diff --git a/.github/renovate.json5 b/.github/renovate.json5 index be3a4d41d..f26f285c2 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -2,13 +2,17 @@ $schema: "https://docs.renovatebot.com/renovate-schema.json", dependencyDashboard: true, suppressNotifications: ["prEditedNotification"], - extends: ["config:recommended"], + extends: [ + "config:recommended", + // For tool versions defined in GitHub Actions: + "customManagers:githubActionsVersions", + ], labels: ["internal"], schedule: ["before 4am on Monday"], semanticCommits: "disabled", separateMajorMinor: false, prHourlyLimit: 10, - enabledManagers: ["github-actions", "pre-commit", "cargo", "regex"], + enabledManagers: ["github-actions", "pre-commit", "cargo", "custom.regex"], cargo: { // See https://docs.renovatebot.com/configuration-options/#rangestrategy rangeStrategy: "update-lockfile", @@ -21,13 +25,13 @@ { // Disable updates of `zip-rs`; intentionally pinned for now due to ownership change // See: https://github.com/astral-sh/uv/issues/3642 - matchPackagePatterns: ["zip"], + matchPackageNames: ["/zip/"], matchManagers: ["cargo"], enabled: false, }, { // Create dedicated branches to update references to dependencies in the documentation. - matchPaths: ["docs/**/*.md"], + matchFileNames: ["docs/**/*.md"], commitMessageTopic: "documentation references to {{{depName}}}", semanticCommitType: "docs", semanticCommitScope: null, @@ -38,7 +42,7 @@ groupName: "Artifact GitHub Actions dependencies", matchManagers: ["github-actions"], matchDatasources: ["gitea-tags", "github-tags"], - matchPackagePatterns: ["actions/.*-artifact"], + matchPackageNames: ["/actions/.*-artifact/"], description: "Weekly update of artifact-related GitHub Actions dependencies", }, { @@ -67,7 +71,7 @@ // of the PEP 440 and PEP 508 crates, which we vendored and forked. groupName: "pyo3", matchManagers: ["cargo"], - matchPackagePatterns: ["pyo3"], + matchPackageNames: ["/pyo3/"], description: "Weekly update of pyo3 dependencies", enabled: false, }, diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index c4ce6a9cd..9ef005234 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -161,16 +161,19 @@ jobs: - alpine:3.20,alpine3.20,alpine - debian:bookworm-slim,bookworm-slim,debian-slim - buildpack-deps:bookworm,bookworm,debian + - python:3.13-alpine,python3.13-alpine - python:3.12-alpine,python3.12-alpine - python:3.11-alpine,python3.11-alpine - python:3.10-alpine,python3.10-alpine - python:3.9-alpine,python3.9-alpine - python:3.8-alpine,python3.8-alpine + - python:3.13-bookworm,python3.13-bookworm - python:3.12-bookworm,python3.12-bookworm - python:3.11-bookworm,python3.11-bookworm - python:3.10-bookworm,python3.10-bookworm - python:3.9-bookworm,python3.9-bookworm - python:3.8-bookworm,python3.8-bookworm + - python:3.13-slim-bookworm,python3.13-bookworm-slim - python:3.12-slim-bookworm,python3.12-bookworm-slim - python:3.11-slim-bookworm,python3.11-bookworm-slim - python:3.10-slim-bookworm,python3.10-bookworm-slim diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92f5b769b..366cd0b2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,6 +81,17 @@ jobs: - name: "Python type check" run: uvx mypy + - name: "Lint shell scripts" + uses: ludeeus/action-shellcheck@2.0.0 + env: + # renovate: datasource=github-tags depName=koalaman/shellcheck + SHELLCHECK_VERSION: "v0.10.0" + SHELLCHECK_OPTS: --shell bash + with: + version: ${{ env.SHELLCHECK_VERSION }} + severity: style + check_together: "yes" + cargo-clippy: timeout-minutes: 10 needs: determine_changes @@ -98,7 +109,8 @@ jobs: run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings cargo-clippy-xwin: - timeout-minutes: 10 + # Do not set timeout below 15 minutes as uncached xwin Windows SDK download can take 10+ minutes + timeout-minutes: 20 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} runs-on: ubuntu-latest @@ -192,8 +204,8 @@ jobs: - name: "Smoke test" run: | uv="./target/debug/uv" - $uv venv - $uv pip install ruff + $uv venv -v + $uv pip install ruff -v - name: "Smoke test completion" run: | @@ -238,8 +250,8 @@ jobs: - name: "Smoke test" run: | uv="./target/debug/uv" - $uv venv - $uv pip install ruff + $uv venv -v + $uv pip install ruff -v cargo-test-windows: timeout-minutes: 15 @@ -296,8 +308,8 @@ jobs: UV_STACK_SIZE: 2000000 # 2 megabyte, double the default on windows run: | Set-Alias -Name uv -Value ./target/debug/uv - uv venv - uv pip install ruff + uv venv -v + uv pip install ruff -v - name: "Smoke test completion" working-directory: ${{ env.UV_WORKSPACE }} @@ -313,7 +325,8 @@ jobs: # Separate jobs for the nightly crate windows-trampoline-check: - timeout-minutes: 10 + # Do not set timeout below 15 minutes as uncached xwin Windows SDK download can take 10+ minutes + timeout-minutes: 20 needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} runs-on: ubuntu-latest @@ -416,6 +429,7 @@ jobs: MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }} steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v3 - uses: actions/setup-python@v5 - name: "Add SSH key" if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} @@ -423,17 +437,12 @@ jobs: with: ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }} - - name: "Install dependencies (public)" - run: pip install -r docs/requirements.txt - name: "Build docs (public)" - run: mkdocs build --strict -f mkdocs.public.yml + run: uvx --with-requirements docs/requirements.txt mkdocs build --strict -f mkdocs.public.yml - - name: "Install dependencies (insiders)" - if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} - run: pip install -r docs/requirements-insiders.txt - name: "Build docs (insiders)" if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} - run: mkdocs build --strict -f mkdocs.insiders.yml + run: uvx --with-requirements docs/requirements.txt mkdocs build --strict -f mkdocs.insiders.yml build-binary-linux: timeout-minutes: 10 @@ -644,7 +653,24 @@ jobs: - name: "Check install" run: | - ./uv pip install anyio + ./uv pip install -v anyio + + - name: "Install free-threaded Python via uv" + run: | + ./uv python install 3.13t + ./uv venv -p 3.13t --python-preference only-managed + + - name: "Check version" + run: | + .venv/bin/python --version + + - name: "Check is free-threaded" + run: | + .venv/bin/python -c "import sys; exit(1) if sys._is_gil_enabled() else exit(0)" + + - name: "Check install" + run: | + ./uv pip install -v anyio integration-test-pypy-linux: timeout-minutes: 10 @@ -783,7 +809,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "graalpy24.0" + python-version: "graalpy24.1" - name: "Download binary" uses: actions/download-artifact@v4 @@ -796,7 +822,6 @@ jobs: - name: Graalpy info run: | which graalpy - echo "GRAAL_PYTHONHOME=$(graalpy -c 'print(__graalpython__.home)')" >> $GITHUB_ENV - name: "Create a virtual environment" run: | @@ -852,7 +877,7 @@ jobs: steps: - uses: timfel/setup-python@fc9bcb4a04f5b1ea7d678c2ca7ea1c479a2468d7 with: - python-version: "graalpy24.0" + python-version: "graalpy24.1" - name: "Download binary" uses: actions/download-artifact@v4 @@ -914,7 +939,7 @@ jobs: steps: - uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.12.7" - name: "Download binary" uses: actions/download-artifact@v4 @@ -946,21 +971,21 @@ jobs: ./uv add anyio - name: "Sync to the system Python" - run: ./uv sync --python 3.12 + run: ./uv sync -v --python 3.12 env: - UV_PROJECT_ENVIRONMENT: "/opt/hostedtoolcache/Python/3.12.6/x64" + UV_PROJECT_ENVIRONMENT: "/opt/hostedtoolcache/Python/3.12.7/x64" - name: "Attempt to sync to the system Python with an incompatible version" run: | - ./uv sync --python 3.11 && { echo "ci: Error; should not succeed"; exit 1; } || { echo "ci: Ok; expected failure"; exit 0; } + ./uv sync -v --python 3.11 && { echo "ci: Error; should not succeed"; exit 1; } || { echo "ci: Ok; expected failure"; exit 0; } env: - UV_PROJECT_ENVIRONMENT: "/opt/hostedtoolcache/Python/3.12.6/x64" + UV_PROJECT_ENVIRONMENT: "/opt/hostedtoolcache/Python/3.12.7/x64" - name: "Attempt to sync to a non-Python environment directory" run: | mkdir -p /home/runner/example touch /home/runner/example/some-file - ./uv sync && { echo "ci: Error; should not succeed"; exit 1; } || { echo "ci: Ok; expected failure"; exit 0; } + ./uv sync -v && { echo "ci: Error; should not succeed"; exit 1; } || { echo "ci: Ok; expected failure"; exit 0; } env: UV_PROJECT_ENVIRONMENT: "/home/runner/example" @@ -987,13 +1012,14 @@ jobs: code: - "crates/uv-publish/**/*" - "scripts/publish/**/*" + - ".github/workflows/ci.yml" integration-test-publish: timeout-minutes: 10 needs: integration-test-publish-changed name: "integration test | uv publish" runs-on: ubuntu-latest - if: ${{ github.repository == 'astral-sh/uv' && (needs.integration-test-publish-changed.outputs.code == 'true' || github.ref == 'refs/heads/main') }} + if: ${{ github.repository == 'astral-sh/uv' && github.event.pull_request.head.repo.fork != true && (needs.integration-test-publish-changed.outputs.code == 'true' || github.ref == 'refs/heads/main') }} environment: uv-test-publish env: # No dbus in GitHub Actions @@ -1034,6 +1060,8 @@ jobs: UV_TEST_PUBLISH_TOKEN: ${{ secrets.UV_TEST_PUBLISH_TOKEN }} UV_TEST_PUBLISH_PASSWORD: ${{ secrets.UV_TEST_PUBLISH_PASSWORD }} UV_TEST_PUBLISH_GITLAB_PAT: ${{ secrets.UV_TEST_PUBLISH_GITLAB_PAT }} + UV_TEST_PUBLISH_CODEBERG_TOKEN: ${{ secrets.UV_TEST_PUBLISH_CODEBERG_TOKEN }} + UV_TEST_PUBLISH_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_PUBLISH_CLOUDSMITH_TOKEN }} cache-test-ubuntu: timeout-minutes: 10 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 5ff44faf2..029a2113e 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -21,14 +21,12 @@ jobs: # For PyPI's trusted publishing. id-token: write steps: + - name: "Install uv" + uses: astral-sh/setup-uv@v3 - uses: actions/download-artifact@v4 with: pattern: wheels-* path: wheels merge-multiple: true - name: Publish to PyPi - uses: pypa/gh-action-pypi-publish@release/v1 - with: - skip-existing: true - packages-dir: wheels - verbose: true + run: uv publish -v wheels/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e05a6b26..6ff89163f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,7 +66,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.22.1/cargo-dist-installer.sh | sh" + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.23.0/cargo-dist-installer.sh | sh" - name: Cache cargo-dist uses: actions/upload-artifact@v4 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 896b3adb7..096af1cbe 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.24.6 + rev: v1.26.0 hooks: - id: typos @@ -42,7 +42,7 @@ repos: types_or: [yaml, json5] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.8 + rev: v0.6.9 hooks: - id: ruff-format - id: ruff diff --git a/.python-versions b/.python-versions index fde50855c..c78335fbd 100644 --- a/.python-versions +++ b/.python-versions @@ -1,6 +1,6 @@ -3.12.1 -3.11.7 -3.10.13 -3.9.18 +3.12.6 +3.11.10 +3.10.15 +3.9.20 3.8.18 3.8.12 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2358af9..571b34c18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,222 @@ # Changelog +## 0.4.23 + +This release introduces a revamped system for defining package indexes, as an alternative to the existing pip-style +`--index-url` and `--extra-index-url` configuration options. + +You can now define named indexes in your `pyproject.toml` file using the `[[tool.uv.index]]` table: + +```toml +[[tool.uv.index]] +name = "pytorch" +url = "https://download.pytorch.org/whl/cpu" +``` + +Packages can be pinned to a specific index via `tool.uv.sources`, to ensure that a given package is installed from the +correct index. For example, to ensure that `torch` is _always_ installed from the `pytorch` index: + +```toml +[tool.uv.sources] +torch = { index = "pytorch" } + +[[tool.uv.index]] +name = "pytorch" +url = "https://download.pytorch.org/whl/cpu" +``` + +Indexes can also be marked as `explicit = true` to prevent packages from being installed from that index +unless explicitly pinned. For example, to ensure that `torch` is installed from the `pytorch` index, but all other +packages are installed from the default index: + +```toml +[tool.uv.sources] +torch = { index = "pytorch" } + +[[tool.uv.index]] +name = "pytorch" +url = "https://download.pytorch.org/whl/cpu" +explicit = true +``` + +To define an additional index outside a `pyproject.toml` file, use the `--index` command-line argument +(or the `UV_INDEX` environment variable); to replace the default index (PyPI), use the `--default-index` command-line +argument (or `UV_DEFAULT_INDEX`). + +These changes are entirely backwards-compatible with the deprecated `--index-url` and `--extra-index-url` options, +which continue to work as before. + +See the [Index](https://docs.astral.sh/uv/configuration/indexes/) documentation for more. + +### Enhancements + +- Add index URLs when provided via `uv add --index` or `--default-index` ([#7746](https://github.com/astral-sh/uv/pull/7746)) +- Add support for named and explicit indexes ([#7481](https://github.com/astral-sh/uv/pull/7481)) +- Add templates for popular build backends ([#7857](https://github.com/astral-sh/uv/pull/7857)) +- Allow multiple pinned indexes in `tool.uv.sources` ([#7769](https://github.com/astral-sh/uv/pull/7769)) +- Allow users to incorporate Git tags into dynamic cache keys ([#8259](https://github.com/astral-sh/uv/pull/8259)) +- Pin named indexes in `uv add` ([#7747](https://github.com/astral-sh/uv/pull/7747)) +- Respect named `--index` and `--default-index` values in `tool.uv.sources` ([#7910](https://github.com/astral-sh/uv/pull/7910)) +- Update to latest PubGrub version ([#8245](https://github.com/astral-sh/uv/pull/8245)) +- Enable environment variable authentication for named indexes ([#7741](https://github.com/astral-sh/uv/pull/7741)) +- Avoid showing lower-bound warning outside of explicit lock and sync ([#8234](https://github.com/astral-sh/uv/pull/8234)) +- Improve logging during lock errors ([#8258](https://github.com/astral-sh/uv/pull/8258)) +- Improve styling of `requires-python` warnings ([#8240](https://github.com/astral-sh/uv/pull/8240)) +- Show hint in resolution failure on `Forbidden` (`403`) or `Unauthorized` (`401`) ([#8264](https://github.com/astral-sh/uv/pull/8264)) +- Update to latest `cargo-dist` version (includes new installer features) ([#8270](https://github.com/astral-sh/uv/pull/8270)) +- Warn when patch version in `requires-python` is implicitly `0` ([#7959](https://github.com/astral-sh/uv/pull/7959)) +- Add more context on client errors during range requests ([#8285](https://github.com/astral-sh/uv/pull/8285)) + +### Bug fixes + +- Avoid writing duplicate index URLs with `--emit-index-url` ([#8226](https://github.com/astral-sh/uv/pull/8226)) +- Fix error leading to out-of-bound panic in `uv-pep508` ([#8282](https://github.com/astral-sh/uv/pull/8282)) +- Fix managed distributions of free-threaded Python on Windows ([#8268](https://github.com/astral-sh/uv/pull/8268)) +- Fix selection of free-threaded interpreters during default Python discovery ([#8239](https://github.com/astral-sh/uv/pull/8239)) +- Ignore sources in build requirements for non-source trees ([#8235](https://github.com/astral-sh/uv/pull/8235)) +- Invalid cache when adding lower bound to lockfile ([#8230](https://github.com/astral-sh/uv/pull/8230)) +- Respect index priority when storing credentials ([#8256](https://github.com/astral-sh/uv/pull/8256)) +- Respect relative paths in `uv build` sources ([#8237](https://github.com/astral-sh/uv/pull/8237)) +- Narrow what the pip3. logic drops from entry points. ([#8273](https://github.com/astral-sh/uv/pull/8273)) + +### Documentation + +- Add some additional notes to `--index-url` docs ([#8267](https://github.com/astral-sh/uv/pull/8267)) +- Add upgrade note to README ([#7937](https://github.com/astral-sh/uv/pull/7937)) +- Remove note that "only a single source may be defined for each dependency" ([#8243](https://github.com/astral-sh/uv/pull/8243)) + +## 0.4.22 + +### Enhancements + +- Respect `[tool.uv.sources]` in build requirements ([#7172](https://github.com/astral-sh/uv/pull/7172)) + +### Preview features + +- Add a dedicated `uv publish` error message for missing usernames ([#8045](https://github.com/astral-sh/uv/pull/8045)) +- Support interactive input in `uv publish` ([#8158](https://github.com/astral-sh/uv/pull/8158)) +- Use raw filenames in `uv publish` ([#8204](https://github.com/astral-sh/uv/pull/8204)) + +### Performance + +- Reuse the result of `which git` ([#8224](https://github.com/astral-sh/uv/pull/8224)) + +### Bug fixes + +- Avoid environment check optimization for `uv pip install --exact` ([#8219](https://github.com/astral-sh/uv/pull/8219)) +- Do not use free-threaded interpreters without a free-threaded request ([#8191](https://github.com/astral-sh/uv/pull/8191)) +- Don't recommend `--prerelease=allow` during build requirement resolution errors ([#8192](https://github.com/astral-sh/uv/pull/8192)) +- Prefer optimized builds for free-threaded Python downloads ([#8196](https://github.com/astral-sh/uv/pull/8196)) +- Retain old `python-build-standalone` releases ([#8216](https://github.com/astral-sh/uv/pull/8216)) +- Run `uv build` builds in the source distribution bucket ([#8220](https://github.com/astral-sh/uv/pull/8220)) + +## 0.4.21 + +### Enhancements + +- Add support for managed installations of free-threaded Python ([#8100](https://github.com/astral-sh/uv/pull/8100)) +- Add note about `uvx` to `uv tool run` short help ([#7695](https://github.com/astral-sh/uv/pull/7695)) +- Enable HTTP/2 requests ([#8049](https://github.com/astral-sh/uv/pull/8049)) +- Support `uv tree --no-dev` ([#8109](https://github.com/astral-sh/uv/pull/8109)) +- Support PEP 723 metadata with `uv run -` ([#8111](https://github.com/astral-sh/uv/pull/8111)) +- Support `pip install --exact` ([#8044](https://github.com/astral-sh/uv/pull/8044)) +- Support `uv export --no-header` ([#8096](https://github.com/astral-sh/uv/pull/8096)) +- Add Python 3.13 images to Docker publish ([#8105](https://github.com/astral-sh/uv/pull/8105)) +- Support remote (`https://`) scripts in `uv run` ([#6375](https://github.com/astral-sh/uv/pull/6375)) +- Allow comma value-delimited arguments in `uv run --with` ([#7909](https://github.com/astral-sh/uv/pull/7909)) + +### Configuration + +- Support wildcards in `UV_INSECURE_HOST` ([#8052](https://github.com/astral-sh/uv/pull/8052)) + +### Performance + +- Use shared index when fetching metadata in lock satisfaction routine ([#8147](https://github.com/astral-sh/uv/pull/8147)) + +### Bug fixes + +- Add prerelease compatibility check to `uv python` CLI ([#8020](https://github.com/astral-sh/uv/pull/8020)) +- Avoid deleting a project environment directory if we cannot tell if a `pyvenv.cfg` file exists ([#8012](https://github.com/astral-sh/uv/pull/8012)) +- Avoid excluding valid wheels for exact `requires-python` bounds ([#8140](https://github.com/astral-sh/uv/pull/8140)) +- Bump `netrc` crate to latest commit ([#8021](https://github.com/astral-sh/uv/pull/8021)) +- Fix `uv python pin 3.13t` failure when parsing version for project requires check ([#8056](https://github.com/astral-sh/uv/pull/8056)) +- Fix handling of != intersections in `requires-python` ([#7897](https://github.com/astral-sh/uv/pull/7897)) +- Remove the newly created tool environment if sync failed ([#8038](https://github.com/astral-sh/uv/pull/8038)) +- Respect dynamic extras in `uv lock` and `uv sync` ([#8091](https://github.com/astral-sh/uv/pull/8091)) +- Treat resolver failures as fatal in lockfile validation ([#8083](https://github.com/astral-sh/uv/pull/8083)) +- Use `git config --get` for author information for improved backwards compatibility ([#8101](https://github.com/astral-sh/uv/pull/8101)) +- Use comma-separated values for `UV_FIND_LINKS` ([#8061](https://github.com/astral-sh/uv/pull/8061)) +- Use shared resolver state between add and lock to avoid double Git update ([#8146](https://github.com/astral-sh/uv/pull/8146)) +- Make `--relocatable` entrypoints robust to symlinking ([#8079](https://github.com/astral-sh/uv/pull/8079)) +- Improve compatibility with VSCode PS1 prompt ([#8006](https://github.com/astral-sh/uv/pull/8006)) +- Fix "Stream did not contain valid UTF-8" failures in Windows ([#8120](https://github.com/astral-sh/uv/pull/8120)) +- Use `--with-requirements` in `uvx` error hint ([#8112](https://github.com/astral-sh/uv/pull/8112)) + +### Documentation + +- Include `uvx` installation in Docker examples ([#8179](https://github.com/astral-sh/uv/pull/8179)) +- Make the instructions for the Windows standalone installer consistent across README and documentation ([#8125](https://github.com/astral-sh/uv/pull/8125)) +- Update pip compatibility guide to note transitive URL dependency support ([#8081](https://github.com/astral-sh/uv/pull/8081)) +- Document `--reinstall` with `--exclude-newer` to ensure downgrades ([#6721](https://github.com/astral-sh/uv/pull/6721)) + +## 0.4.20 + +### Enhancements + +- Add managed downloads for CPython 3.13.0 (final) ([#8010](https://github.com/astral-sh/uv/pull/8010)) +- Python 3.13 is the default version for `uv python install` ([#8010](https://github.com/astral-sh/uv/pull/8010)) +- Hint at wrong endpoint in `uv publish` failures ([#7872](https://github.com/astral-sh/uv/pull/7872)) +- List available scripts when a command is not specified for `uv run` ([#7687](https://github.com/astral-sh/uv/pull/7687)) +- Fill in `authors` field during `uv init` ([#7756](https://github.com/astral-sh/uv/pull/7756)) + +### Documentation + +- Add snapshot testing to contribution guide ([#7882](https://github.com/astral-sh/uv/pull/7882)) +- Fix and improve GitLab integration docs ([#8000](https://github.com/astral-sh/uv/pull/8000)) + +## 0.4.19 + +### Enhancements + +- Add managed downloads for CPython 3.13.0rc3 and 3.12.7 ([#7880](https://github.com/astral-sh/uv/pull/7880)) +- Display the target virtual environment path if non-default ([#7850](https://github.com/astral-sh/uv/pull/7850)) +- Preserve case-insensitive sorts in `uv add` ([#7864](https://github.com/astral-sh/uv/pull/7864)) +- Respect project upper bounds when filtering wheels on `requires-python` ([#7904](https://github.com/astral-sh/uv/pull/7904)) +- Add `--script` to `uv run` to treat an input as PEP 723 regardless of extension ([#7739](https://github.com/astral-sh/uv/pull/7739)) +- Improve legibility of build failure errors ([#7854](https://github.com/astral-sh/uv/pull/7854)) +- Show interpreter source during Python discovery query errors ([#7928](https://github.com/astral-sh/uv/pull/7928)) + +### Configuration + +- Add `UV_FIND_LINKS` environment variable for `--find-links` ([#7912](https://github.com/astral-sh/uv/pull/7912)) +- Ignore empty string values for `UV_PYTHON` environment variable ([#7878](https://github.com/astral-sh/uv/pull/7878)) + +### Bug fixes + +- Allow `py3x-none` tags in newer than Python 3.x ([#7867](https://github.com/astral-sh/uv/pull/7867)) +- Allow self-dependencies in the `dev` section ([#7943](https://github.com/astral-sh/uv/pull/7943)) +- Always ignore `cp2` wheels in resolution ([#7902](https://github.com/astral-sh/uv/pull/7902)) +- Clear the publish progress bar on retry ([#7921](https://github.com/astral-sh/uv/pull/7921)) +- Fix parsing of `gnueabi` libc variants in Python version requests ([#7975](https://github.com/astral-sh/uv/pull/7975)) +- Simplify supported environments when comparing to lockfile ([#7894](https://github.com/astral-sh/uv/pull/7894)) +- Trim commits when reading from Git refs ([#7922](https://github.com/astral-sh/uv/pull/7922)) +- Use a higher HTTP read timeout when publishing packages ([#7923](https://github.com/astral-sh/uv/pull/7923)) +- Remove the first empty line for `uv tree --package foo` ([#7885](https://github.com/astral-sh/uv/pull/7885)) + +### Documentation + +- Add 3.13 support to the platform reference ([#7971](https://github.com/astral-sh/uv/pull/7971)) +- Clarify project environment creation ([#7941](https://github.com/astral-sh/uv/pull/7941)) +- Fix code block title in Gitlab integration docs ([#7861](https://github.com/astral-sh/uv/pull/7861)) +- Fix project guide section on adding a Git dependency ([#7916](https://github.com/astral-sh/uv/pull/7916)) +- Fix uninstallation command for Windows ([#7944](https://github.com/astral-sh/uv/pull/7944)) +- Clearly specify the minimum supported Windows Server version ([#7946](https://github.com/astral-sh/uv/pull/7946)) + +### Rust API + +- Remove unused `Sha256Reader` ([#7929](https://github.com/astral-sh/uv/pull/7929)) +- Remove unnecessary `Deserialize` derives on settings ([#7856](https://github.com/astral-sh/uv/pull/7856)) + ## 0.4.18 ### Enhancements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96285ed14..053f66dd6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,6 +49,30 @@ cargo run python install The storage directory can be configured with `UV_PYTHON_INSTALL_DIR`. +### Snapshot testing + +uv uses [insta](https://insta.rs/) for snapshot testing. It's recommended (but not necessary) to use +`cargo-insta` for a better snapshot review experience. See the +[installation guide](https://insta.rs/docs/cli/) for more information. + +In tests, you can use `uv_snapshot!` macro to simplify creating snapshots for uv commands. For +example: + +```rust +#[test] +fn test_add() { + let context = TestContext::new("3.12"); + uv_snapshot!(context.filters(), context.add().arg("requests"), @""); +} +``` + +To run and review a specific snapshot test: + +```shell +cargo test --package --test -- -- --exact +cargo insta review +``` + ### Local testing You can invoke your development version of uv with `cargo run -- `. For example: diff --git a/Cargo.lock b/Cargo.lock index e0add76a3..aea58177e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,13 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -160,9 +154,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.12" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +checksum = "103db485efc3e41214fe4fda9f3dbeae2eb9082f48fd236e6095627a9422066e" dependencies = [ "bzip2", "flate2", @@ -229,9 +223,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axoasset" @@ -314,7 +308,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.8.0", + "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -361,9 +355,9 @@ dependencies = [ [[package]] name = "boxcar" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510a90332002c1af3317ef6b712f0dab697f30bbe809b86965eac2923c0bca8e" +checksum = "fba19c552ee63cb6646b75e1166d1bdb8a6d34a6d19e319dec88c8adadff2db3" [[package]] name = "bstr" @@ -407,9 +401,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" [[package]] name = "byteorder" @@ -425,9 +419,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bzip2" @@ -499,9 +493,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.19" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "jobserver", "libc", @@ -559,9 +553,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -569,22 +563,22 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", - "terminal_size", + "terminal_size 0.4.0", ] [[package]] name = "clap_complete" -version = "4.5.26" +version = "4.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "205d5ef6d485fa47606b98b0ddc4ead26eb850aaa86abfb562a94fb3280ecba0" +checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb" dependencies = [ "clap", ] @@ -602,9 +596,9 @@ dependencies = [ [[package]] name = "clap_complete_nushell" -version = "4.5.3" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe32110e006bccf720f8c9af3fee1ba7db290c724eab61544e1d3295be3a40e" +checksum = "315902e790cc6e5ddd20cbd313c1d0d49db77f191e149f96397230fb82a17677" dependencies = [ "clap", "clap_complete", @@ -847,7 +841,7 @@ version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" dependencies = [ - "nix 0.29.0", + "nix", "windows-sys 0.59.0", ] @@ -859,7 +853,7 @@ checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core 0.9.10", @@ -1052,9 +1046,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" dependencies = [ "simd-adler32", ] @@ -1085,7 +1079,7 @@ checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "libz-ng-sys", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -1155,9 +1149,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1170,9 +1164,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1180,15 +1174,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1197,9 +1191,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1216,9 +1210,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -1227,21 +1221,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1290,9 +1284,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -1370,6 +1364,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.5.0" @@ -1405,12 +1405,12 @@ dependencies = [ [[package]] name = "homedir" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bed305c13ce3829a09d627f5d43ff738482a09361ae4eb8039993b55fb10e5e" +checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" dependencies = [ "cfg-if", - "nix 0.26.4", + "nix", "widestring", "windows 0.57.0", ] @@ -1460,15 +1460,15 @@ dependencies = [ [[package]] name = "http-content-range" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f0d1a8ef218a86416107794b34cc446958d9203556c312bb41eab4c924c1d2e" +checksum = "aa7929c876417cd3ece616950474c7dff5b0150a2b53bd7e7fda55afa086c22b" [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1478,9 +1478,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -1508,7 +1508,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", - "rustls-native-certs 0.8.0", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1580,12 +1580,12 @@ checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", "serde", ] @@ -1638,9 +1638,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" @@ -1741,9 +1741,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1800,9 +1800,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libmimalloc-sys" @@ -1822,7 +1822,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", ] [[package]] @@ -1939,15 +1939,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "miette" version = "7.2.0" @@ -1960,7 +1951,7 @@ dependencies = [ "supports-color", "supports-hyperlinks", "supports-unicode", - "terminal_size", + "terminal_size 0.3.0", "textwrap", "thiserror", "unicode-width", @@ -2002,16 +1993,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", - "simd-adler32", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -2019,6 +2000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -2071,19 +2053,6 @@ dependencies = [ "rand", ] -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset", - "pin-utils", -] - [[package]] name = "nix" version = "0.29.0" @@ -2148,18 +2117,18 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" @@ -2240,7 +2209,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -2259,9 +2228,9 @@ checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" [[package]] name = "percent-encoding" @@ -2271,9 +2240,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", "thiserror", @@ -2282,9 +2251,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -2292,9 +2261,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", @@ -2305,9 +2274,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -2332,18 +2301,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", @@ -2370,9 +2339,9 @@ checksum = "d15b6607fa632996eb8a17c9041cb6071cb75ac057abd45dece578723ea8c7c0" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plain" @@ -2392,15 +2361,15 @@ dependencies = [ [[package]] name = "png" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide", ] [[package]] @@ -2414,9 +2383,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" @@ -2469,9 +2438,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560bcab673ff7f6ca9e270c17bf3affd8a05e3bd9207f123b0d45076fd8197e8" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", @@ -2480,9 +2449,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -2510,7 +2479,7 @@ dependencies = [ [[package]] name = "pubgrub" version = "0.2.1" -source = "git+https://github.com/astral-sh/pubgrub?rev=388685a8711092971930986644cfed152d1a1f6c#388685a8711092971930986644cfed152d1a1f6c" +source = "git+https://github.com/astral-sh/pubgrub?rev=19c77268c0ad5f69d7e12126e0cfacfbba466481#19c77268c0ad5f69d7e12126e0cfacfbba466481" dependencies = [ "indexmap", "log", @@ -2667,9 +2636,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -2742,18 +2711,18 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rend" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c1f1959e4db12c985c0283656be0925f1539549db1e47c4bd0b8b599e1ef7" +checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "async-compression", "base64 0.22.1", @@ -2761,6 +2730,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -2777,7 +2747,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-native-certs 0.7.3", + "rustls-native-certs", "rustls-pemfile", "rustls-pki-types", "serde", @@ -2893,7 +2863,7 @@ checksum = "395027076c569819ea6035ee62e664f5e03d74e281744f55261dd1afd939212b" dependencies = [ "bytecheck", "bytes", - "hashbrown", + "hashbrown 0.14.5", "indexmap", "munge", "ptr_meta", @@ -2968,8 +2938,7 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rust-netrc" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32662f97cbfdbad9d5f78f1338116f06871e7dae4fd37e9f59a0f57cf2044868" +source = "git+https://github.com/gribouille/netrc?rev=544f3890b621f0dc30fcefb4f804269c160ce2e9#544f3890b621f0dc30fcefb4f804269c160ce2e9" dependencies = [ "thiserror", ] @@ -3001,9 +2970,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" dependencies = [ "once_cell", "ring", @@ -3013,19 +2982,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework", -] - [[package]] name = "rustls-native-certs" version = "0.8.0" @@ -3041,19 +2997,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -3099,9 +3054,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -3178,9 +3133,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -3248,9 +3203,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -3316,9 +3271,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" @@ -3372,6 +3327,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spdx" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc" +dependencies = [ + "smallvec", +] + [[package]] name = "spin" version = "0.9.8" @@ -3530,6 +3494,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "terminal_size" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termtree" version = "0.4.1" @@ -3963,9 +3937,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" @@ -3978,9 +3952,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-bidi-mirroring" @@ -4014,18 +3988,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-script" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-vo" @@ -4035,9 +4009,9 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unscanny" @@ -4116,13 +4090,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[package]] name = "uv" -version = "0.4.18" +version = "0.4.23" dependencies = [ "anstream", "anyhow", @@ -4132,6 +4106,7 @@ dependencies = [ "base64 0.22.1", "byteorder", "clap", + "console", "ctrlc", "etcetera", "filetime", @@ -4168,12 +4143,14 @@ dependencies = [ "unicode-width", "url", "uv-auth", + "uv-build-backend", "uv-cache", "uv-cache-info", "uv-cache-key", "uv-cli", "uv-client", "uv-configuration", + "uv-console", "uv-dispatch", "uv-distribution", "uv-distribution-filename", @@ -4197,8 +4174,10 @@ dependencies = [ "uv-scripts", "uv-settings", "uv-shell", + "uv-static", "uv-tool", "uv-types", + "uv-version", "uv-virtualenv", "uv-warnings", "uv-workspace", @@ -4227,6 +4206,7 @@ dependencies = [ "url", "urlencoding", "uv-once-map", + "uv-static", "wiremock", ] @@ -4257,6 +4237,35 @@ dependencies = [ "uv-types", ] +[[package]] +name = "uv-build-backend" +version = "0.1.0" +dependencies = [ + "csv", + "fs-err", + "glob", + "indoc", + "insta", + "itertools 0.13.0", + "serde", + "sha2", + "spdx", + "tempfile", + "thiserror", + "toml", + "tracing", + "uv-distribution-filename", + "uv-fs", + "uv-normalize", + "uv-pep440", + "uv-pep508", + "uv-pubgrub", + "uv-pypi-types", + "uv-warnings", + "walkdir", + "zip", +] + [[package]] name = "uv-build-frontend" version = "0.0.1" @@ -4278,12 +4287,14 @@ dependencies = [ "toml_edit", "tracing", "uv-configuration", + "uv-distribution", "uv-distribution-types", "uv-fs", "uv-pep440", "uv-pep508", "uv-pypi-types", "uv-python", + "uv-static", "uv-types", "uv-virtualenv", ] @@ -4309,6 +4320,7 @@ dependencies = [ "uv-fs", "uv-normalize", "uv-pypi-types", + "uv-static", "walkdir", ] @@ -4356,6 +4368,7 @@ dependencies = [ "uv-python", "uv-resolver", "uv-settings", + "uv-static", "uv-version", "uv-warnings", ] @@ -4407,6 +4420,7 @@ dependencies = [ "uv-pep508", "uv-platform-tags", "uv-pypi-types", + "uv-static", "uv-version", "uv-warnings", ] @@ -4435,6 +4449,7 @@ dependencies = [ "uv-pep508", "uv-platform-tags", "uv-pypi-types", + "uv-static", "which", ] @@ -4482,6 +4497,7 @@ dependencies = [ "uv-pypi-types", "uv-python", "uv-settings", + "uv-static", "uv-workspace", "walkdir", ] @@ -4575,6 +4591,7 @@ name = "uv-distribution-types" version = "0.0.1" dependencies = [ "anyhow", + "bitflags 2.6.0", "fs-err", "itertools 0.13.0", "jiff", @@ -4587,6 +4604,7 @@ dependencies = [ "tracing", "url", "urlencoding", + "uv-auth", "uv-cache-info", "uv-cache-key", "uv-distribution-filename", @@ -4636,12 +4654,14 @@ dependencies = [ "fs2", "junction", "path-slash", + "rustix", "schemars", "serde", "tempfile", "tokio", "tracing", "urlencoding", + "winsafe 0.0.22", ] [[package]] @@ -4662,6 +4682,8 @@ dependencies = [ "uv-auth", "uv-cache-key", "uv-fs", + "uv-static", + "which", ] [[package]] @@ -4732,6 +4754,7 @@ dependencies = [ "uv-platform-tags", "uv-pypi-types", "uv-python", + "uv-static", "uv-types", "uv-warnings", "walkdir", @@ -4892,6 +4915,7 @@ dependencies = [ "uv-fs", "uv-metadata", "uv-pypi-types", + "uv-static", "uv-warnings", ] @@ -4939,7 +4963,6 @@ dependencies = [ "reqwest", "reqwest-middleware", "rmp-serde", - "rustix", "same-file", "schemars", "serde", @@ -4966,12 +4989,12 @@ dependencies = [ "uv-platform-tags", "uv-pypi-types", "uv-state", + "uv-static", "uv-warnings", "which", "windows-registry", "windows-result 0.2.0", "windows-sys 0.59.0", - "winsafe 0.0.22", ] [[package]] @@ -5085,6 +5108,7 @@ dependencies = [ "uv-pypi-types", "uv-python", "uv-requirements-txt", + "uv-static", "uv-types", "uv-warnings", "uv-workspace", @@ -5100,6 +5124,7 @@ dependencies = [ "serde", "thiserror", "toml", + "uv-distribution-types", "uv-pep440", "uv-pep508", "uv-pypi-types", @@ -5133,6 +5158,7 @@ dependencies = [ "uv-pypi-types", "uv-python", "uv-resolver", + "uv-static", "uv-warnings", ] @@ -5145,6 +5171,7 @@ dependencies = [ "same-file", "tracing", "uv-fs", + "uv-static", "winreg", ] @@ -5158,6 +5185,10 @@ dependencies = [ "tempfile", ] +[[package]] +name = "uv-static" +version = "0.0.1" + [[package]] name = "uv-tool" version = "0.0.1" @@ -5180,6 +5211,7 @@ dependencies = [ "uv-python", "uv-settings", "uv-state", + "uv-static", "uv-virtualenv", ] @@ -5205,7 +5237,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.4.18" +version = "0.4.23" [[package]] name = "uv-virtualenv" @@ -5257,6 +5289,7 @@ dependencies = [ "toml_edit", "tracing", "url", + "uv-distribution-types", "uv-fs", "uv-git", "uv-macros", @@ -5265,6 +5298,7 @@ dependencies = [ "uv-pep440", "uv-pep508", "uv-pypi-types", + "uv-static", "uv-warnings", ] @@ -5316,9 +5350,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -5327,9 +5361,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -5342,9 +5376,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -5354,9 +5388,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5364,9 +5398,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -5377,15 +5411,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -5411,9 +5445,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -5421,9 +5455,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -5762,9 +5796,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b363b49ef..3046f7f8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ license = "MIT OR Apache-2.0" [workspace.dependencies] uv-auth = { path = "crates/uv-auth" } +uv-build-backend = { path = "crates/uv-build-backend" } uv-build-frontend = { path = "crates/uv-build-frontend" } uv-cache = { path = "crates/uv-cache" } uv-cache-info = { path = "crates/uv-cache-info" } @@ -44,7 +45,7 @@ uv-metadata = { path = "crates/uv-metadata" } uv-normalize = { path = "crates/uv-normalize" } uv-once-map = { path = "crates/uv-once-map" } uv-options-metadata = { path = "crates/uv-options-metadata" } -uv-pep440 = { path = "crates/uv-pep440" } +uv-pep440 = { path = "crates/uv-pep440", features = ["tracing", "rkyv"] } uv-pep508 = { path = "crates/uv-pep508", features = ["non-pep508-extensions"] } uv-platform-tags = { path = "crates/uv-platform-tags" } uv-pubgrub = { path = "crates/uv-pubgrub" } @@ -58,6 +59,7 @@ uv-scripts = { path = "crates/uv-scripts" } uv-settings = { path = "crates/uv-settings" } uv-shell = { path = "crates/uv-shell" } uv-state = { path = "crates/uv-state" } +uv-static = { path = "crates/uv-static" } uv-tool = { path = "crates/uv-tool" } uv-types = { path = "crates/uv-types" } uv-version = { path = "crates/uv-version" } @@ -75,6 +77,7 @@ async_zip = { git = "https://github.com/charliermarsh/rs-async-zip", rev = "011b axoupdater = { version = "0.7.2", default-features = false } backoff = { version = "0.4.0" } base64 = { version = "0.22.1" } +bitflags = { version = "2.6.0" } boxcar = { version = "0.2.5" } bytecheck = { version = "0.8.0" } cachedir = { version = "0.3.1" } @@ -122,17 +125,17 @@ pathdiff = { version = "0.2.1" } petgraph = { version = "0.6.5" } platform-info = { version = "2.0.3" } proc-macro2 = { version = "1.0.86" } -pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "388685a8711092971930986644cfed152d1a1f6c" } +pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "19c77268c0ad5f69d7e12126e0cfacfbba466481" } quote = { version = "1.0.37" } rayon = { version = "1.10.0" } reflink-copy = { version = "0.1.19" } regex = { version = "1.10.6" } -reqwest = { version = "0.12.7", default-features = false, features = ["json", "gzip", "stream", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart"] } +reqwest = { version = "0.12.7", default-features = false, features = ["json", "gzip", "stream", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2"] } reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "5e3eaf254b5bd481c75d2710eed055f95b756913", features = ["multipart"] } reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "5e3eaf254b5bd481c75d2710eed055f95b756913" } rkyv = { version = "0.8.8", features = ["bytecheck"] } rmp-serde = { version = "1.3.0" } -rust-netrc = { version = "0.1.1" } +rust-netrc = { git = "https://github.com/gribouille/netrc", rev = "544f3890b621f0dc30fcefb4f804269c160ce2e9" } rustc-hash = { version = "2.0.0" } rustix = { version = "0.38.37", default-features = false, features = ["fs", "std"] } same-file = { version = "1.0.6" } @@ -143,6 +146,7 @@ serde-untagged = { version = "0.1.6" } serde_json = { version = "1.0.128" } sha2 = { version = "0.10.8" } smallvec = { version = "1.13.2" } +spdx = { version = "0.10.6" } syn = { version = "2.0.77" } sys-info = { version = "0.9.1" } target-lexicon = { version = "0.12.16" } @@ -276,7 +280,7 @@ inherits = "release" # Config for 'cargo dist' [workspace.metadata.dist] # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) -cargo-dist-version = "0.22.1" +cargo-dist-version = "0.23.0" # CI backends to support ci = "github" # The installers to generate for each app diff --git a/README.md b/README.md index c541a5a2b..186c4af7e 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,18 @@ Install uv with our standalone installers, or from [PyPI](https://pypi.org/proje $ curl -LsSf https://astral.sh/uv/install.sh | sh # On Windows. -$ powershell -c "irm https://astral.sh/uv/install.ps1 | iex" +$ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # With pip. $ pip install uv ``` +If installed via the standalone installer, uv can update itself to the latest version: + +```console +$ uv self update +``` + See the [installation documentation](https://docs.astral.sh/uv/getting-started/installation/) for details and alternative installation methods. diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index ca73428bf..24d75582b 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -3,6 +3,9 @@ name = "uv-auth" version = "0.0.1" edition = "2021" +[lib] +doctest = false + [lints] workspace = true @@ -23,6 +26,8 @@ tracing = { workspace = true } url = { workspace = true } urlencoding = { workspace = true } +uv-static = { workspace = true } + [dev-dependencies] tempfile = { workspace = true } tokio = { workspace = true } diff --git a/crates/uv-auth/src/cache.rs b/crates/uv-auth/src/cache.rs index 008744ece..f62c69d05 100644 --- a/crates/uv-auth/src/cache.rs +++ b/crates/uv-auth/src/cache.rs @@ -215,77 +215,4 @@ impl TrieState { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_trie() { - let credentials1 = Arc::new(Credentials::new( - Some("username1".to_string()), - Some("password1".to_string()), - )); - let credentials2 = Arc::new(Credentials::new( - Some("username2".to_string()), - Some("password2".to_string()), - )); - let credentials3 = Arc::new(Credentials::new( - Some("username3".to_string()), - Some("password3".to_string()), - )); - let credentials4 = Arc::new(Credentials::new( - Some("username4".to_string()), - Some("password4".to_string()), - )); - - let mut trie = UrlTrie::new(); - trie.insert( - &Url::parse("https://burntsushi.net").unwrap(), - credentials1.clone(), - ); - trie.insert( - &Url::parse("https://astral.sh").unwrap(), - credentials2.clone(), - ); - trie.insert( - &Url::parse("https://example.com/foo").unwrap(), - credentials3.clone(), - ); - trie.insert( - &Url::parse("https://example.com/bar").unwrap(), - credentials4.clone(), - ); - - let url = Url::parse("https://burntsushi.net/regex-internals").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials1)); - - let url = Url::parse("https://burntsushi.net/").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials1)); - - let url = Url::parse("https://astral.sh/about").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials2)); - - let url = Url::parse("https://example.com/foo").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials3)); - - let url = Url::parse("https://example.com/foo/").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials3)); - - let url = Url::parse("https://example.com/foo/bar").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials3)); - - let url = Url::parse("https://example.com/bar").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials4)); - - let url = Url::parse("https://example.com/bar/").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials4)); - - let url = Url::parse("https://example.com/bar/foo").unwrap(); - assert_eq!(trie.get(&url), Some(&credentials4)); - - let url = Url::parse("https://example.com/about").unwrap(); - assert_eq!(trie.get(&url), None); - - let url = Url::parse("https://example.com/foobar").unwrap(); - assert_eq!(trie.get(&url), None); - } -} +mod tests; diff --git a/crates/uv-auth/src/cache/tests.rs b/crates/uv-auth/src/cache/tests.rs new file mode 100644 index 000000000..2975e0e23 --- /dev/null +++ b/crates/uv-auth/src/cache/tests.rs @@ -0,0 +1,72 @@ +use super::*; + +#[test] +fn test_trie() { + let credentials1 = Arc::new(Credentials::new( + Some("username1".to_string()), + Some("password1".to_string()), + )); + let credentials2 = Arc::new(Credentials::new( + Some("username2".to_string()), + Some("password2".to_string()), + )); + let credentials3 = Arc::new(Credentials::new( + Some("username3".to_string()), + Some("password3".to_string()), + )); + let credentials4 = Arc::new(Credentials::new( + Some("username4".to_string()), + Some("password4".to_string()), + )); + + let mut trie = UrlTrie::new(); + trie.insert( + &Url::parse("https://burntsushi.net").unwrap(), + credentials1.clone(), + ); + trie.insert( + &Url::parse("https://astral.sh").unwrap(), + credentials2.clone(), + ); + trie.insert( + &Url::parse("https://example.com/foo").unwrap(), + credentials3.clone(), + ); + trie.insert( + &Url::parse("https://example.com/bar").unwrap(), + credentials4.clone(), + ); + + let url = Url::parse("https://burntsushi.net/regex-internals").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials1)); + + let url = Url::parse("https://burntsushi.net/").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials1)); + + let url = Url::parse("https://astral.sh/about").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials2)); + + let url = Url::parse("https://example.com/foo").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials3)); + + let url = Url::parse("https://example.com/foo/").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials3)); + + let url = Url::parse("https://example.com/foo/bar").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials3)); + + let url = Url::parse("https://example.com/bar").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials4)); + + let url = Url::parse("https://example.com/bar/").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials4)); + + let url = Url::parse("https://example.com/bar/foo").unwrap(); + assert_eq!(trie.get(&url), Some(&credentials4)); + + let url = Url::parse("https://example.com/about").unwrap(); + assert_eq!(trie.get(&url), None); + + let url = Url::parse("https://example.com/foobar").unwrap(); + assert_eq!(trie.get(&url), None); +} diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index 0c301dcec..91aa48103 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -9,6 +9,8 @@ use std::io::Read; use std::io::Write; use url::Url; +use uv_static::EnvVars; + #[derive(Clone, Debug, PartialEq)] pub struct Credentials { /// The name of the user for authentication. @@ -139,6 +141,21 @@ impl Credentials { }) } + /// Extract the [`Credentials`] from the environment, given a named source. + /// + /// For example, given a name of `"pytorch"`, search for `UV_HTTP_BASIC_PYTORCH_USERNAME` and + /// `UV_HTTP_BASIC_PYTORCH_PASSWORD`. + pub fn from_env(name: &str) -> Option { + let name = name.to_uppercase(); + let username = std::env::var(EnvVars::http_basic_username(&name)).ok(); + let password = std::env::var(EnvVars::http_basic_password(&name)).ok(); + if username.is_none() && password.is_none() { + None + } else { + Some(Self::new(username, password)) + } + } + /// Parse [`Credentials`] from an HTTP request, if any. /// /// Only HTTP Basic Authentication is supported. @@ -230,111 +247,4 @@ impl Credentials { } #[cfg(test)] -mod test { - use insta::assert_debug_snapshot; - - use super::*; - - #[test] - fn from_url_no_credentials() { - let url = &Url::parse("https://example.com/simple/first/").unwrap(); - assert_eq!(Credentials::from_url(url), None); - } - - #[test] - fn from_url_username_and_password() { - let url = &Url::parse("https://example.com/simple/first/").unwrap(); - let mut auth_url = url.clone(); - auth_url.set_username("user").unwrap(); - auth_url.set_password(Some("password")).unwrap(); - let credentials = Credentials::from_url(&auth_url).unwrap(); - assert_eq!(credentials.username(), Some("user")); - assert_eq!(credentials.password(), Some("password")); - } - - #[test] - fn from_url_no_username() { - let url = &Url::parse("https://example.com/simple/first/").unwrap(); - let mut auth_url = url.clone(); - auth_url.set_password(Some("password")).unwrap(); - let credentials = Credentials::from_url(&auth_url).unwrap(); - assert_eq!(credentials.username(), None); - assert_eq!(credentials.password(), Some("password")); - } - - #[test] - fn from_url_no_password() { - let url = &Url::parse("https://example.com/simple/first/").unwrap(); - let mut auth_url = url.clone(); - auth_url.set_username("user").unwrap(); - let credentials = Credentials::from_url(&auth_url).unwrap(); - assert_eq!(credentials.username(), Some("user")); - assert_eq!(credentials.password(), None); - } - - #[test] - fn authenticated_request_from_url() { - let url = Url::parse("https://example.com/simple/first/").unwrap(); - let mut auth_url = url.clone(); - auth_url.set_username("user").unwrap(); - auth_url.set_password(Some("password")).unwrap(); - let credentials = Credentials::from_url(&auth_url).unwrap(); - - let mut request = reqwest::Request::new(reqwest::Method::GET, url); - request = credentials.authenticate(request); - - let mut header = request - .headers() - .get(reqwest::header::AUTHORIZATION) - .expect("Authorization header should be set") - .clone(); - header.set_sensitive(false); - - assert_debug_snapshot!(header, @r###""Basic dXNlcjpwYXNzd29yZA==""###); - assert_eq!(Credentials::from_header_value(&header), Some(credentials)); - } - - #[test] - fn authenticated_request_from_url_with_percent_encoded_user() { - let url = Url::parse("https://example.com/simple/first/").unwrap(); - let mut auth_url = url.clone(); - auth_url.set_username("user@domain").unwrap(); - auth_url.set_password(Some("password")).unwrap(); - let credentials = Credentials::from_url(&auth_url).unwrap(); - - let mut request = reqwest::Request::new(reqwest::Method::GET, url); - request = credentials.authenticate(request); - - let mut header = request - .headers() - .get(reqwest::header::AUTHORIZATION) - .expect("Authorization header should be set") - .clone(); - header.set_sensitive(false); - - assert_debug_snapshot!(header, @r###""Basic dXNlckBkb21haW46cGFzc3dvcmQ=""###); - assert_eq!(Credentials::from_header_value(&header), Some(credentials)); - } - - #[test] - fn authenticated_request_from_url_with_percent_encoded_password() { - let url = Url::parse("https://example.com/simple/first/").unwrap(); - let mut auth_url = url.clone(); - auth_url.set_username("user").unwrap(); - auth_url.set_password(Some("password==")).unwrap(); - let credentials = Credentials::from_url(&auth_url).unwrap(); - - let mut request = reqwest::Request::new(reqwest::Method::GET, url); - request = credentials.authenticate(request); - - let mut header = request - .headers() - .get(reqwest::header::AUTHORIZATION) - .expect("Authorization header should be set") - .clone(); - header.set_sensitive(false); - - assert_debug_snapshot!(header, @r###""Basic dXNlcjpwYXNzd29yZD09""###); - assert_eq!(Credentials::from_header_value(&header), Some(credentials)); - } -} +mod tests; diff --git a/crates/uv-auth/src/credentials/tests.rs b/crates/uv-auth/src/credentials/tests.rs new file mode 100644 index 000000000..dd4e373ae --- /dev/null +++ b/crates/uv-auth/src/credentials/tests.rs @@ -0,0 +1,106 @@ +use insta::assert_debug_snapshot; + +use super::*; + +#[test] +fn from_url_no_credentials() { + let url = &Url::parse("https://example.com/simple/first/").unwrap(); + assert_eq!(Credentials::from_url(url), None); +} + +#[test] +fn from_url_username_and_password() { + let url = &Url::parse("https://example.com/simple/first/").unwrap(); + let mut auth_url = url.clone(); + auth_url.set_username("user").unwrap(); + auth_url.set_password(Some("password")).unwrap(); + let credentials = Credentials::from_url(&auth_url).unwrap(); + assert_eq!(credentials.username(), Some("user")); + assert_eq!(credentials.password(), Some("password")); +} + +#[test] +fn from_url_no_username() { + let url = &Url::parse("https://example.com/simple/first/").unwrap(); + let mut auth_url = url.clone(); + auth_url.set_password(Some("password")).unwrap(); + let credentials = Credentials::from_url(&auth_url).unwrap(); + assert_eq!(credentials.username(), None); + assert_eq!(credentials.password(), Some("password")); +} + +#[test] +fn from_url_no_password() { + let url = &Url::parse("https://example.com/simple/first/").unwrap(); + let mut auth_url = url.clone(); + auth_url.set_username("user").unwrap(); + let credentials = Credentials::from_url(&auth_url).unwrap(); + assert_eq!(credentials.username(), Some("user")); + assert_eq!(credentials.password(), None); +} + +#[test] +fn authenticated_request_from_url() { + let url = Url::parse("https://example.com/simple/first/").unwrap(); + let mut auth_url = url.clone(); + auth_url.set_username("user").unwrap(); + auth_url.set_password(Some("password")).unwrap(); + let credentials = Credentials::from_url(&auth_url).unwrap(); + + let mut request = reqwest::Request::new(reqwest::Method::GET, url); + request = credentials.authenticate(request); + + let mut header = request + .headers() + .get(reqwest::header::AUTHORIZATION) + .expect("Authorization header should be set") + .clone(); + header.set_sensitive(false); + + assert_debug_snapshot!(header, @r###""Basic dXNlcjpwYXNzd29yZA==""###); + assert_eq!(Credentials::from_header_value(&header), Some(credentials)); +} + +#[test] +fn authenticated_request_from_url_with_percent_encoded_user() { + let url = Url::parse("https://example.com/simple/first/").unwrap(); + let mut auth_url = url.clone(); + auth_url.set_username("user@domain").unwrap(); + auth_url.set_password(Some("password")).unwrap(); + let credentials = Credentials::from_url(&auth_url).unwrap(); + + let mut request = reqwest::Request::new(reqwest::Method::GET, url); + request = credentials.authenticate(request); + + let mut header = request + .headers() + .get(reqwest::header::AUTHORIZATION) + .expect("Authorization header should be set") + .clone(); + header.set_sensitive(false); + + assert_debug_snapshot!(header, @r###""Basic dXNlckBkb21haW46cGFzc3dvcmQ=""###); + assert_eq!(Credentials::from_header_value(&header), Some(credentials)); +} + +#[test] +fn authenticated_request_from_url_with_percent_encoded_password() { + let url = Url::parse("https://example.com/simple/first/").unwrap(); + let mut auth_url = url.clone(); + auth_url.set_username("user").unwrap(); + auth_url.set_password(Some("password==")).unwrap(); + let credentials = Credentials::from_url(&auth_url).unwrap(); + + let mut request = reqwest::Request::new(reqwest::Method::GET, url); + request = credentials.authenticate(request); + + let mut header = request + .headers() + .get(reqwest::header::AUTHORIZATION) + .expect("Authorization header should be set") + .clone(); + header.set_sensitive(false); + + assert_debug_snapshot!(header, @r###""Basic dXNlcjpwYXNzd29yZD09""###); + assert_eq!(Credentials::from_header_value(&header), Some(credentials)); +} diff --git a/crates/uv-auth/src/keyring.rs b/crates/uv-auth/src/keyring.rs index 16569d269..bc10c10c7 100644 --- a/crates/uv-auth/src/keyring.rs +++ b/crates/uv-auth/src/keyring.rs @@ -151,133 +151,4 @@ impl KeyringProvider { } #[cfg(test)] -mod test { - use super::*; - use futures::FutureExt; - - #[tokio::test] - async fn fetch_url_no_host() { - let url = Url::parse("file:/etc/bin/").unwrap(); - let keyring = KeyringProvider::empty(); - // Panics due to debug assertion; returns `None` in production - let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, "user")) - .catch_unwind() - .await; - assert!(result.is_err()); - } - - #[tokio::test] - async fn fetch_url_with_password() { - let url = Url::parse("https://user:password@example.com").unwrap(); - let keyring = KeyringProvider::empty(); - // Panics due to debug assertion; returns `None` in production - let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, url.username())) - .catch_unwind() - .await; - assert!(result.is_err()); - } - - #[tokio::test] - async fn fetch_url_with_no_username() { - let url = Url::parse("https://example.com").unwrap(); - let keyring = KeyringProvider::empty(); - // Panics due to debug assertion; returns `None` in production - let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, url.username())) - .catch_unwind() - .await; - assert!(result.is_err()); - } - - #[tokio::test] - async fn fetch_url_no_auth() { - let url = Url::parse("https://example.com").unwrap(); - let keyring = KeyringProvider::empty(); - let credentials = keyring.fetch(&url, "user"); - assert!(credentials.await.is_none()); - } - - #[tokio::test] - async fn fetch_url() { - let url = Url::parse("https://example.com").unwrap(); - let keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "user"), "password")]); - assert_eq!( - keyring.fetch(&url, "user").await, - Some(Credentials::new( - Some("user".to_string()), - Some("password".to_string()) - )) - ); - assert_eq!( - keyring.fetch(&url.join("test").unwrap(), "user").await, - Some(Credentials::new( - Some("user".to_string()), - Some("password".to_string()) - )) - ); - } - - #[tokio::test] - async fn fetch_url_no_match() { - let url = Url::parse("https://example.com").unwrap(); - let keyring = KeyringProvider::dummy([(("other.com", "user"), "password")]); - let credentials = keyring.fetch(&url, "user").await; - assert_eq!(credentials, None); - } - - #[tokio::test] - async fn fetch_url_prefers_url_to_host() { - let url = Url::parse("https://example.com/").unwrap(); - let keyring = KeyringProvider::dummy([ - ((url.join("foo").unwrap().as_str(), "user"), "password"), - ((url.host_str().unwrap(), "user"), "other-password"), - ]); - assert_eq!( - keyring.fetch(&url.join("foo").unwrap(), "user").await, - Some(Credentials::new( - Some("user".to_string()), - Some("password".to_string()) - )) - ); - assert_eq!( - keyring.fetch(&url, "user").await, - Some(Credentials::new( - Some("user".to_string()), - Some("other-password".to_string()) - )) - ); - assert_eq!( - keyring.fetch(&url.join("bar").unwrap(), "user").await, - Some(Credentials::new( - Some("user".to_string()), - Some("other-password".to_string()) - )) - ); - } - - #[tokio::test] - async fn fetch_url_username() { - let url = Url::parse("https://example.com").unwrap(); - let keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "user"), "password")]); - let credentials = keyring.fetch(&url, "user").await; - assert_eq!( - credentials, - Some(Credentials::new( - Some("user".to_string()), - Some("password".to_string()) - )) - ); - } - - #[tokio::test] - async fn fetch_url_username_no_match() { - let url = Url::parse("https://example.com").unwrap(); - let keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "foo"), "password")]); - let credentials = keyring.fetch(&url, "bar").await; - assert_eq!(credentials, None); - - // Still fails if we have `foo` in the URL itself - let url = Url::parse("https://foo@example.com").unwrap(); - let credentials = keyring.fetch(&url, "bar").await; - assert_eq!(credentials, None); - } -} +mod tests; diff --git a/crates/uv-auth/src/keyring/tests.rs b/crates/uv-auth/src/keyring/tests.rs new file mode 100644 index 000000000..6b1e8d6e2 --- /dev/null +++ b/crates/uv-auth/src/keyring/tests.rs @@ -0,0 +1,128 @@ +use super::*; +use futures::FutureExt; + +#[tokio::test] +async fn fetch_url_no_host() { + let url = Url::parse("file:/etc/bin/").unwrap(); + let keyring = KeyringProvider::empty(); + // Panics due to debug assertion; returns `None` in production + let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, "user")) + .catch_unwind() + .await; + assert!(result.is_err()); +} + +#[tokio::test] +async fn fetch_url_with_password() { + let url = Url::parse("https://user:password@example.com").unwrap(); + let keyring = KeyringProvider::empty(); + // Panics due to debug assertion; returns `None` in production + let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, url.username())) + .catch_unwind() + .await; + assert!(result.is_err()); +} + +#[tokio::test] +async fn fetch_url_with_no_username() { + let url = Url::parse("https://example.com").unwrap(); + let keyring = KeyringProvider::empty(); + // Panics due to debug assertion; returns `None` in production + let result = std::panic::AssertUnwindSafe(keyring.fetch(&url, url.username())) + .catch_unwind() + .await; + assert!(result.is_err()); +} + +#[tokio::test] +async fn fetch_url_no_auth() { + let url = Url::parse("https://example.com").unwrap(); + let keyring = KeyringProvider::empty(); + let credentials = keyring.fetch(&url, "user"); + assert!(credentials.await.is_none()); +} + +#[tokio::test] +async fn fetch_url() { + let url = Url::parse("https://example.com").unwrap(); + let keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "user"), "password")]); + assert_eq!( + keyring.fetch(&url, "user").await, + Some(Credentials::new( + Some("user".to_string()), + Some("password".to_string()) + )) + ); + assert_eq!( + keyring.fetch(&url.join("test").unwrap(), "user").await, + Some(Credentials::new( + Some("user".to_string()), + Some("password".to_string()) + )) + ); +} + +#[tokio::test] +async fn fetch_url_no_match() { + let url = Url::parse("https://example.com").unwrap(); + let keyring = KeyringProvider::dummy([(("other.com", "user"), "password")]); + let credentials = keyring.fetch(&url, "user").await; + assert_eq!(credentials, None); +} + +#[tokio::test] +async fn fetch_url_prefers_url_to_host() { + let url = Url::parse("https://example.com/").unwrap(); + let keyring = KeyringProvider::dummy([ + ((url.join("foo").unwrap().as_str(), "user"), "password"), + ((url.host_str().unwrap(), "user"), "other-password"), + ]); + assert_eq!( + keyring.fetch(&url.join("foo").unwrap(), "user").await, + Some(Credentials::new( + Some("user".to_string()), + Some("password".to_string()) + )) + ); + assert_eq!( + keyring.fetch(&url, "user").await, + Some(Credentials::new( + Some("user".to_string()), + Some("other-password".to_string()) + )) + ); + assert_eq!( + keyring.fetch(&url.join("bar").unwrap(), "user").await, + Some(Credentials::new( + Some("user".to_string()), + Some("other-password".to_string()) + )) + ); +} + +#[tokio::test] +async fn fetch_url_username() { + let url = Url::parse("https://example.com").unwrap(); + let keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "user"), "password")]); + let credentials = keyring.fetch(&url, "user").await; + assert_eq!( + credentials, + Some(Credentials::new( + Some("user".to_string()), + Some("password".to_string()) + )) + ); +} + +#[tokio::test] +async fn fetch_url_username_no_match() { + let url = Url::parse("https://example.com").unwrap(); + let keyring = KeyringProvider::dummy([((url.host_str().unwrap(), "foo"), "password")]); + let credentials = keyring.fetch(&url, "bar").await; + assert_eq!(credentials, None); + + // Still fails if we have `foo` in the URL itself + let url = Url::parse("https://foo@example.com").unwrap(); + let credentials = keyring.fetch(&url, "bar").await; + assert_eq!(credentials, None); +} diff --git a/crates/uv-auth/src/lib.rs b/crates/uv-auth/src/lib.rs index 61c1c2825..16f644418 100644 --- a/crates/uv-auth/src/lib.rs +++ b/crates/uv-auth/src/lib.rs @@ -35,3 +35,11 @@ pub fn store_credentials_from_url(url: &Url) -> bool { false } } + +/// Populate the global authentication store with credentials on a URL, if there are any. +/// +/// Returns `true` if the store was updated. +pub fn store_credentials(url: &Url, credentials: Credentials) { + trace!("Caching credentials for {url}"); + CREDENTIALS_CACHE.insert(url, Arc::new(credentials)); +} diff --git a/crates/uv-auth/src/middleware.rs b/crates/uv-auth/src/middleware.rs index cd6b17749..c2651c835 100644 --- a/crates/uv-auth/src/middleware.rs +++ b/crates/uv-auth/src/middleware.rs @@ -407,1084 +407,4 @@ impl AuthMiddleware { } #[cfg(test)] -mod tests { - - use std::io::Write; - - use reqwest::Client; - use tempfile::NamedTempFile; - use test_log::test; - - use url::Url; - use wiremock::matchers::{basic_auth, method, path_regex}; - use wiremock::{Mock, MockServer, ResponseTemplate}; - - use super::*; - - type Error = Box; - - async fn start_test_server(username: &'static str, password: &'static str) -> MockServer { - let server = MockServer::start().await; - - Mock::given(method("GET")) - .and(basic_auth(username, password)) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - Mock::given(method("GET")) - .respond_with(ResponseTemplate::new(401)) - .mount(&server) - .await; - - server - } - - fn test_client_builder() -> reqwest_middleware::ClientBuilder { - reqwest_middleware::ClientBuilder::new( - Client::builder() - .build() - .expect("Reqwest client should build"), - ) - } - - #[test(tokio::test)] - async fn test_no_credentials() -> Result<(), Error> { - let server = start_test_server("user", "password").await; - let client = test_client_builder() - .with(AuthMiddleware::new().with_cache(CredentialsCache::new())) - .build(); - - assert_eq!( - client - .get(format!("{}/foo", server.uri())) - .send() - .await? - .status(), - 401 - ); - - assert_eq!( - client - .get(format!("{}/bar", server.uri())) - .send() - .await? - .status(), - 401 - ); - - Ok(()) - } - - /// Without seeding the cache, authenticated requests are not cached - #[test(tokio::test)] - async fn test_credentials_in_url_no_seed() -> Result<(), Error> { - let username = "user"; - let password = "password"; - - let server = start_test_server(username, password).await; - let client = test_client_builder() - .with(AuthMiddleware::new().with_cache(CredentialsCache::new())) - .build(); - - let base_url = Url::parse(&server.uri())?; - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - url.set_password(Some(password)).unwrap(); - assert_eq!(client.get(url).send().await?.status(), 200); - - // Works for a URL without credentials now - assert_eq!( - client.get(server.uri()).send().await?.status(), - 200, - "Subsequent requests should not require credentials" - ); - - assert_eq!( - client - .get(format!("{}/foo", server.uri())) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same realm" - ); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - url.set_password(Some("invalid")).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "Credentials in the URL should take precedence and fail" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_credentials_in_url_seed() -> Result<(), Error> { - let username = "user"; - let password = "password"; - - let server = start_test_server(username, password).await; - let base_url = Url::parse(&server.uri())?; - let cache = CredentialsCache::new(); - cache.insert( - &base_url, - Arc::new(Credentials::new( - Some(username.to_string()), - Some(password.to_string()), - )), - ); - - let client = test_client_builder() - .with(AuthMiddleware::new().with_cache(cache)) - .build(); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - url.set_password(Some(password)).unwrap(); - assert_eq!(client.get(url).send().await?.status(), 200); - - // Works for a URL without credentials too - assert_eq!( - client.get(server.uri()).send().await?.status(), - 200, - "Requests should not require credentials" - ); - - assert_eq!( - client - .get(format!("{}/foo", server.uri())) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same realm" - ); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - url.set_password(Some("invalid")).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "Credentials in the URL should take precedence and fail" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_credentials_in_url_username_only() -> Result<(), Error> { - let username = "user"; - let password = ""; - - let server = start_test_server(username, password).await; - let base_url = Url::parse(&server.uri())?; - let cache = CredentialsCache::new(); - cache.insert( - &base_url, - Arc::new(Credentials::new(Some(username.to_string()), None)), - ); - - let client = test_client_builder() - .with(AuthMiddleware::new().with_cache(cache)) - .build(); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - url.set_password(None).unwrap(); - assert_eq!(client.get(url).send().await?.status(), 200); - - // Works for a URL without credentials too - assert_eq!( - client.get(server.uri()).send().await?.status(), - 200, - "Requests should not require credentials" - ); - - assert_eq!( - client - .get(format!("{}/foo", server.uri())) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same realm" - ); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - url.set_password(Some("invalid")).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "Credentials in the URL should take precedence and fail" - ); - - assert_eq!( - client.get(server.uri()).send().await?.status(), - 200, - "Subsequent requests should not use the invalid credentials" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_netrc_file_default_host() -> Result<(), Error> { - let username = "user"; - let password = "password"; - - let mut netrc_file = NamedTempFile::new()?; - writeln!(netrc_file, "default login {username} password {password}")?; - - let server = start_test_server(username, password).await; - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_netrc(Netrc::from_file(netrc_file.path()).ok()), - ) - .build(); - - assert_eq!( - client.get(server.uri()).send().await?.status(), - 200, - "Credentials should be pulled from the netrc file" - ); - - let mut url = Url::parse(&server.uri())?; - url.set_username(username).unwrap(); - url.set_password(Some("invalid")).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "Credentials in the URL should take precedence and fail" - ); - - assert_eq!( - client.get(server.uri()).send().await?.status(), - 200, - "Subsequent requests should not use the invalid credentials" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_netrc_file_matching_host() -> Result<(), Error> { - let username = "user"; - let password = "password"; - let server = start_test_server(username, password).await; - let base_url = Url::parse(&server.uri())?; - - let mut netrc_file = NamedTempFile::new()?; - writeln!( - netrc_file, - r#"machine {} login {username} password {password}"#, - base_url.host_str().unwrap() - )?; - - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_netrc(Some( - Netrc::from_file(netrc_file.path()).expect("Test has valid netrc file"), - )), - ) - .build(); - - assert_eq!( - client.get(server.uri()).send().await?.status(), - 200, - "Credentials should be pulled from the netrc file" - ); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - url.set_password(Some("invalid")).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "Credentials in the URL should take precedence and fail" - ); - - assert_eq!( - client.get(server.uri()).send().await?.status(), - 200, - "Subsequent requests should not use the invalid credentials" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_netrc_file_mismatched_host() -> Result<(), Error> { - let username = "user"; - let password = "password"; - let server = start_test_server(username, password).await; - - let mut netrc_file = NamedTempFile::new()?; - writeln!( - netrc_file, - r#"machine example.com login {username} password {password}"#, - )?; - - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_netrc(Some( - Netrc::from_file(netrc_file.path()).expect("Test has valid netrc file"), - )), - ) - .build(); - - assert_eq!( - client.get(server.uri()).send().await?.status(), - 401, - "Credentials should not be pulled from the netrc file due to host mismatch" - ); - - let mut url = Url::parse(&server.uri())?; - url.set_username(username).unwrap(); - url.set_password(Some(password)).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 200, - "Credentials in the URL should still work" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_netrc_file_mismatched_username() -> Result<(), Error> { - let username = "user"; - let password = "password"; - let server = start_test_server(username, password).await; - let base_url = Url::parse(&server.uri())?; - - let mut netrc_file = NamedTempFile::new()?; - writeln!( - netrc_file, - r#"machine {} login {username} password {password}"#, - base_url.host_str().unwrap() - )?; - - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_netrc(Some( - Netrc::from_file(netrc_file.path()).expect("Test has valid netrc file"), - )), - ) - .build(); - - let mut url = base_url.clone(); - url.set_username("other-user").unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "The netrc password should not be used due to a username mismatch" - ); - - let mut url = base_url.clone(); - url.set_username("user").unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 200, - "The netrc password should be used for a matching user" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_keyring() -> Result<(), Error> { - let username = "user"; - let password = "password"; - let server = start_test_server(username, password).await; - let base_url = Url::parse(&server.uri())?; - - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_keyring(Some(KeyringProvider::dummy([( - ( - format!( - "{}:{}", - base_url.host_str().unwrap(), - base_url.port().unwrap() - ), - username, - ), - password, - )]))), - ) - .build(); - - assert_eq!( - client.get(server.uri()).send().await?.status(), - 401, - "Credentials are not pulled from the keyring without a username" - ); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 200, - "Credentials for the username should be pulled from the keyring" - ); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - url.set_password(Some("invalid")).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "Password in the URL should take precedence and fail" - ); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - assert_eq!( - client.get(url.clone()).send().await?.status(), - 200, - "Subsequent requests should not use the invalid password" - ); - - let mut url = base_url.clone(); - url.set_username("other_user").unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "Credentials are not pulled from the keyring when given another username" - ); - - Ok(()) - } - - /// We include ports in keyring requests, e.g., `localhost:8000` should be distinct from `localhost`, - /// unless the server is running on a default port, e.g., `localhost:80` is equivalent to `localhost`. - /// We don't unit test the latter case because it's possible to collide with a server a developer is - /// actually running. - #[test(tokio::test)] - async fn test_keyring_includes_non_standard_port() -> Result<(), Error> { - let username = "user"; - let password = "password"; - let server = start_test_server(username, password).await; - let base_url = Url::parse(&server.uri())?; - - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_keyring(Some(KeyringProvider::dummy([( - // Omit the port from the keyring entry - (base_url.host_str().unwrap(), username), - password, - )]))), - ) - .build(); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 401, - "We should fail because the port is not present in the keyring entry" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_credentials_in_keyring_seed() -> Result<(), Error> { - let username = "user"; - let password = "password"; - - let server = start_test_server(username, password).await; - let base_url = Url::parse(&server.uri())?; - let cache = CredentialsCache::new(); - - // Seed _just_ the username. This cache entry should be ignored and we should - // still find a password via the keyring. - cache.insert( - &base_url, - Arc::new(Credentials::new(Some(username.to_string()), None)), - ); - let client = test_client_builder() - .with(AuthMiddleware::new().with_cache(cache).with_keyring(Some( - KeyringProvider::dummy([( - ( - format!( - "{}:{}", - base_url.host_str().unwrap(), - base_url.port().unwrap() - ), - username, - ), - password, - )]), - ))) - .build(); - - assert_eq!( - client.get(server.uri()).send().await?.status(), - 401, - "Credentials are not pulled from the keyring without a username" - ); - - let mut url = base_url.clone(); - url.set_username(username).unwrap(); - assert_eq!( - client.get(url).send().await?.status(), - 200, - "Credentials for the username should be pulled from the keyring" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_credentials_in_url_multiple_realms() -> Result<(), Error> { - let username_1 = "user1"; - let password_1 = "password1"; - let server_1 = start_test_server(username_1, password_1).await; - let base_url_1 = Url::parse(&server_1.uri())?; - - let username_2 = "user2"; - let password_2 = "password2"; - let server_2 = start_test_server(username_2, password_2).await; - let base_url_2 = Url::parse(&server_2.uri())?; - - let cache = CredentialsCache::new(); - // Seed the cache with our credentials - cache.insert( - &base_url_1, - Arc::new(Credentials::new( - Some(username_1.to_string()), - Some(password_1.to_string()), - )), - ); - cache.insert( - &base_url_2, - Arc::new(Credentials::new( - Some(username_2.to_string()), - Some(password_2.to_string()), - )), - ); - - let client = test_client_builder() - .with(AuthMiddleware::new().with_cache(cache)) - .build(); - - // Both servers should work - assert_eq!( - client.get(server_1.uri()).send().await?.status(), - 200, - "Requests should not require credentials" - ); - assert_eq!( - client.get(server_2.uri()).send().await?.status(), - 200, - "Requests should not require credentials" - ); - - assert_eq!( - client - .get(format!("{}/foo", server_1.uri())) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same realm" - ); - assert_eq!( - client - .get(format!("{}/foo", server_2.uri())) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same realm" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_credentials_from_keyring_multiple_realms() -> Result<(), Error> { - let username_1 = "user1"; - let password_1 = "password1"; - let server_1 = start_test_server(username_1, password_1).await; - let base_url_1 = Url::parse(&server_1.uri())?; - - let username_2 = "user2"; - let password_2 = "password2"; - let server_2 = start_test_server(username_2, password_2).await; - let base_url_2 = Url::parse(&server_2.uri())?; - - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_keyring(Some(KeyringProvider::dummy([ - ( - ( - format!( - "{}:{}", - base_url_1.host_str().unwrap(), - base_url_1.port().unwrap() - ), - username_1, - ), - password_1, - ), - ( - ( - format!( - "{}:{}", - base_url_2.host_str().unwrap(), - base_url_2.port().unwrap() - ), - username_2, - ), - password_2, - ), - ]))), - ) - .build(); - - // Both servers do not work without a username - assert_eq!( - client.get(server_1.uri()).send().await?.status(), - 401, - "Requests should require a username" - ); - assert_eq!( - client.get(server_2.uri()).send().await?.status(), - 401, - "Requests should require a username" - ); - - let mut url_1 = base_url_1.clone(); - url_1.set_username(username_1).unwrap(); - assert_eq!( - client.get(url_1.clone()).send().await?.status(), - 200, - "Requests with a username should succeed" - ); - assert_eq!( - client.get(server_2.uri()).send().await?.status(), - 401, - "Credentials should not be re-used for the second server" - ); - - let mut url_2 = base_url_2.clone(); - url_2.set_username(username_2).unwrap(); - assert_eq!( - client.get(url_2.clone()).send().await?.status(), - 200, - "Requests with a username should succeed" - ); - - assert_eq!( - client.get(format!("{url_1}/foo")).send().await?.status(), - 200, - "Requests can be to different paths in the same realm" - ); - assert_eq!( - client.get(format!("{url_2}/foo")).send().await?.status(), - 200, - "Requests can be to different paths in the same realm" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_credentials_in_url_mixed_authentication_in_realm() -> Result<(), Error> { - let username_1 = "user1"; - let password_1 = "password1"; - let username_2 = "user2"; - let password_2 = "password2"; - - let server = MockServer::start().await; - - Mock::given(method("GET")) - .and(path_regex("/prefix_1.*")) - .and(basic_auth(username_1, password_1)) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - Mock::given(method("GET")) - .and(path_regex("/prefix_2.*")) - .and(basic_auth(username_2, password_2)) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - // Create a third, public prefix - // It will throw a 401 if it receives credentials - Mock::given(method("GET")) - .and(path_regex("/prefix_3.*")) - .and(basic_auth(username_1, password_1)) - .respond_with(ResponseTemplate::new(401)) - .mount(&server) - .await; - Mock::given(method("GET")) - .and(path_regex("/prefix_3.*")) - .and(basic_auth(username_2, password_2)) - .respond_with(ResponseTemplate::new(401)) - .mount(&server) - .await; - Mock::given(method("GET")) - .and(path_regex("/prefix_3.*")) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - Mock::given(method("GET")) - .respond_with(ResponseTemplate::new(401)) - .mount(&server) - .await; - - let base_url = Url::parse(&server.uri())?; - let base_url_1 = base_url.join("prefix_1")?; - let base_url_2 = base_url.join("prefix_2")?; - let base_url_3 = base_url.join("prefix_3")?; - - let cache = CredentialsCache::new(); - - // Seed the cache with our credentials - cache.insert( - &base_url_1, - Arc::new(Credentials::new( - Some(username_1.to_string()), - Some(password_1.to_string()), - )), - ); - cache.insert( - &base_url_2, - Arc::new(Credentials::new( - Some(username_2.to_string()), - Some(password_2.to_string()), - )), - ); - - let client = test_client_builder() - .with(AuthMiddleware::new().with_cache(cache)) - .build(); - - // Both servers should work - assert_eq!( - client.get(base_url_1.clone()).send().await?.status(), - 200, - "Requests should not require credentials" - ); - assert_eq!( - client.get(base_url_2.clone()).send().await?.status(), - 200, - "Requests should not require credentials" - ); - assert_eq!( - client - .get(base_url.join("prefix_1/foo")?) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same realm" - ); - assert_eq!( - client - .get(base_url.join("prefix_2/foo")?) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same realm" - ); - assert_eq!( - client - .get(base_url.join("prefix_1_foo")?) - .send() - .await? - .status(), - 401, - "Requests to paths with a matching prefix but different resource segments should fail" - ); - - assert_eq!( - client.get(base_url_3.clone()).send().await?.status(), - 200, - "Requests to the 'public' prefix should not use credentials" - ); - - Ok(()) - } - - #[test(tokio::test)] - async fn test_credentials_from_keyring_mixed_authentication_in_realm() -> Result<(), Error> { - let username_1 = "user1"; - let password_1 = "password1"; - let username_2 = "user2"; - let password_2 = "password2"; - - let server = MockServer::start().await; - - Mock::given(method("GET")) - .and(path_regex("/prefix_1.*")) - .and(basic_auth(username_1, password_1)) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - Mock::given(method("GET")) - .and(path_regex("/prefix_2.*")) - .and(basic_auth(username_2, password_2)) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - // Create a third, public prefix - // It will throw a 401 if it receives credentials - Mock::given(method("GET")) - .and(path_regex("/prefix_3.*")) - .and(basic_auth(username_1, password_1)) - .respond_with(ResponseTemplate::new(401)) - .mount(&server) - .await; - Mock::given(method("GET")) - .and(path_regex("/prefix_3.*")) - .and(basic_auth(username_2, password_2)) - .respond_with(ResponseTemplate::new(401)) - .mount(&server) - .await; - Mock::given(method("GET")) - .and(path_regex("/prefix_3.*")) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - Mock::given(method("GET")) - .respond_with(ResponseTemplate::new(401)) - .mount(&server) - .await; - - let base_url = Url::parse(&server.uri())?; - let base_url_1 = base_url.join("prefix_1")?; - let base_url_2 = base_url.join("prefix_2")?; - let base_url_3 = base_url.join("prefix_3")?; - - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_keyring(Some(KeyringProvider::dummy([ - ( - ( - format!( - "{}:{}", - base_url_1.host_str().unwrap(), - base_url_1.port().unwrap() - ), - username_1, - ), - password_1, - ), - ( - ( - format!( - "{}:{}", - base_url_2.host_str().unwrap(), - base_url_2.port().unwrap() - ), - username_2, - ), - password_2, - ), - ]))), - ) - .build(); - - // Both servers do not work without a username - assert_eq!( - client.get(base_url_1.clone()).send().await?.status(), - 401, - "Requests should require a username" - ); - assert_eq!( - client.get(base_url_2.clone()).send().await?.status(), - 401, - "Requests should require a username" - ); - - let mut url_1 = base_url_1.clone(); - url_1.set_username(username_1).unwrap(); - assert_eq!( - client.get(url_1.clone()).send().await?.status(), - 200, - "Requests with a username should succeed" - ); - assert_eq!( - client.get(base_url_2.clone()).send().await?.status(), - 401, - "Credentials should not be re-used for the second prefix" - ); - - let mut url_2 = base_url_2.clone(); - url_2.set_username(username_2).unwrap(); - assert_eq!( - client.get(url_2.clone()).send().await?.status(), - 200, - "Requests with a username should succeed" - ); - - assert_eq!( - client - .get(base_url.join("prefix_1/foo")?) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same prefix" - ); - assert_eq!( - client - .get(base_url.join("prefix_2/foo")?) - .send() - .await? - .status(), - 200, - "Requests can be to different paths in the same prefix" - ); - assert_eq!( - client - .get(base_url.join("prefix_1_foo")?) - .send() - .await? - .status(), - 401, - "Requests to paths with a matching prefix but different resource segments should fail" - ); - assert_eq!( - client.get(base_url_3.clone()).send().await?.status(), - 200, - "Requests to the 'public' prefix should not use credentials" - ); - - Ok(()) - } - - /// Demonstrates "incorrect" behavior in our cache which avoids an expensive fetch of - /// credentials for _every_ request URL at the cost of inconsistent behavior when - /// credentials are not scoped to a realm. - #[test(tokio::test)] - async fn test_credentials_from_keyring_mixed_authentication_in_realm_same_username( - ) -> Result<(), Error> { - let username = "user"; - let password_1 = "password1"; - let password_2 = "password2"; - - let server = MockServer::start().await; - - Mock::given(method("GET")) - .and(path_regex("/prefix_1.*")) - .and(basic_auth(username, password_1)) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - Mock::given(method("GET")) - .and(path_regex("/prefix_2.*")) - .and(basic_auth(username, password_2)) - .respond_with(ResponseTemplate::new(200)) - .mount(&server) - .await; - - Mock::given(method("GET")) - .respond_with(ResponseTemplate::new(401)) - .mount(&server) - .await; - - let base_url = Url::parse(&server.uri())?; - let base_url_1 = base_url.join("prefix_1")?; - let base_url_2 = base_url.join("prefix_2")?; - - let client = test_client_builder() - .with( - AuthMiddleware::new() - .with_cache(CredentialsCache::new()) - .with_keyring(Some(KeyringProvider::dummy([ - ((base_url_1.clone(), username), password_1), - ((base_url_2.clone(), username), password_2), - ]))), - ) - .build(); - - // Both servers do not work without a username - assert_eq!( - client.get(base_url_1.clone()).send().await?.status(), - 401, - "Requests should require a username" - ); - assert_eq!( - client.get(base_url_2.clone()).send().await?.status(), - 401, - "Requests should require a username" - ); - - let mut url_1 = base_url_1.clone(); - url_1.set_username(username).unwrap(); - assert_eq!( - client.get(url_1.clone()).send().await?.status(), - 200, - "The first request with a username will succeed" - ); - assert_eq!( - client.get(base_url_2.clone()).send().await?.status(), - 401, - "Credentials should not be re-used for the second prefix" - ); - assert_eq!( - client - .get(base_url.join("prefix_1/foo")?) - .send() - .await? - .status(), - 200, - "Subsequent requests can be to different paths in the same prefix" - ); - - let mut url_2 = base_url_2.clone(); - url_2.set_username(username).unwrap(); - assert_eq!( - client.get(url_2.clone()).send().await?.status(), - 401, // INCORRECT BEHAVIOR - "A request with the same username and realm for a URL that needs a different password will fail" - ); - assert_eq!( - client - .get(base_url.join("prefix_2/foo")?) - .send() - .await? - .status(), - 401, // INCORRECT BEHAVIOR - "Requests to other paths in the failing prefix will also fail" - ); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-auth/src/middleware/tests.rs b/crates/uv-auth/src/middleware/tests.rs new file mode 100644 index 000000000..f4ad7af25 --- /dev/null +++ b/crates/uv-auth/src/middleware/tests.rs @@ -0,0 +1,1079 @@ +use std::io::Write; + +use reqwest::Client; +use tempfile::NamedTempFile; +use test_log::test; + +use url::Url; +use wiremock::matchers::{basic_auth, method, path_regex}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +use super::*; + +type Error = Box; + +async fn start_test_server(username: &'static str, password: &'static str) -> MockServer { + let server = MockServer::start().await; + + Mock::given(method("GET")) + .and(basic_auth(username, password)) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + + server +} + +fn test_client_builder() -> reqwest_middleware::ClientBuilder { + reqwest_middleware::ClientBuilder::new( + Client::builder() + .build() + .expect("Reqwest client should build"), + ) +} + +#[test(tokio::test)] +async fn test_no_credentials() -> Result<(), Error> { + let server = start_test_server("user", "password").await; + let client = test_client_builder() + .with(AuthMiddleware::new().with_cache(CredentialsCache::new())) + .build(); + + assert_eq!( + client + .get(format!("{}/foo", server.uri())) + .send() + .await? + .status(), + 401 + ); + + assert_eq!( + client + .get(format!("{}/bar", server.uri())) + .send() + .await? + .status(), + 401 + ); + + Ok(()) +} + +/// Without seeding the cache, authenticated requests are not cached +#[test(tokio::test)] +async fn test_credentials_in_url_no_seed() -> Result<(), Error> { + let username = "user"; + let password = "password"; + + let server = start_test_server(username, password).await; + let client = test_client_builder() + .with(AuthMiddleware::new().with_cache(CredentialsCache::new())) + .build(); + + let base_url = Url::parse(&server.uri())?; + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + url.set_password(Some(password)).unwrap(); + assert_eq!(client.get(url).send().await?.status(), 200); + + // Works for a URL without credentials now + assert_eq!( + client.get(server.uri()).send().await?.status(), + 200, + "Subsequent requests should not require credentials" + ); + + assert_eq!( + client + .get(format!("{}/foo", server.uri())) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same realm" + ); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + url.set_password(Some("invalid")).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "Credentials in the URL should take precedence and fail" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_credentials_in_url_seed() -> Result<(), Error> { + let username = "user"; + let password = "password"; + + let server = start_test_server(username, password).await; + let base_url = Url::parse(&server.uri())?; + let cache = CredentialsCache::new(); + cache.insert( + &base_url, + Arc::new(Credentials::new( + Some(username.to_string()), + Some(password.to_string()), + )), + ); + + let client = test_client_builder() + .with(AuthMiddleware::new().with_cache(cache)) + .build(); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + url.set_password(Some(password)).unwrap(); + assert_eq!(client.get(url).send().await?.status(), 200); + + // Works for a URL without credentials too + assert_eq!( + client.get(server.uri()).send().await?.status(), + 200, + "Requests should not require credentials" + ); + + assert_eq!( + client + .get(format!("{}/foo", server.uri())) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same realm" + ); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + url.set_password(Some("invalid")).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "Credentials in the URL should take precedence and fail" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_credentials_in_url_username_only() -> Result<(), Error> { + let username = "user"; + let password = ""; + + let server = start_test_server(username, password).await; + let base_url = Url::parse(&server.uri())?; + let cache = CredentialsCache::new(); + cache.insert( + &base_url, + Arc::new(Credentials::new(Some(username.to_string()), None)), + ); + + let client = test_client_builder() + .with(AuthMiddleware::new().with_cache(cache)) + .build(); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + url.set_password(None).unwrap(); + assert_eq!(client.get(url).send().await?.status(), 200); + + // Works for a URL without credentials too + assert_eq!( + client.get(server.uri()).send().await?.status(), + 200, + "Requests should not require credentials" + ); + + assert_eq!( + client + .get(format!("{}/foo", server.uri())) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same realm" + ); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + url.set_password(Some("invalid")).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "Credentials in the URL should take precedence and fail" + ); + + assert_eq!( + client.get(server.uri()).send().await?.status(), + 200, + "Subsequent requests should not use the invalid credentials" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_netrc_file_default_host() -> Result<(), Error> { + let username = "user"; + let password = "password"; + + let mut netrc_file = NamedTempFile::new()?; + writeln!(netrc_file, "default login {username} password {password}")?; + + let server = start_test_server(username, password).await; + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_netrc(Netrc::from_file(netrc_file.path()).ok()), + ) + .build(); + + assert_eq!( + client.get(server.uri()).send().await?.status(), + 200, + "Credentials should be pulled from the netrc file" + ); + + let mut url = Url::parse(&server.uri())?; + url.set_username(username).unwrap(); + url.set_password(Some("invalid")).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "Credentials in the URL should take precedence and fail" + ); + + assert_eq!( + client.get(server.uri()).send().await?.status(), + 200, + "Subsequent requests should not use the invalid credentials" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_netrc_file_matching_host() -> Result<(), Error> { + let username = "user"; + let password = "password"; + let server = start_test_server(username, password).await; + let base_url = Url::parse(&server.uri())?; + + let mut netrc_file = NamedTempFile::new()?; + writeln!( + netrc_file, + r#"machine {} login {username} password {password}"#, + base_url.host_str().unwrap() + )?; + + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_netrc(Some( + Netrc::from_file(netrc_file.path()).expect("Test has valid netrc file"), + )), + ) + .build(); + + assert_eq!( + client.get(server.uri()).send().await?.status(), + 200, + "Credentials should be pulled from the netrc file" + ); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + url.set_password(Some("invalid")).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "Credentials in the URL should take precedence and fail" + ); + + assert_eq!( + client.get(server.uri()).send().await?.status(), + 200, + "Subsequent requests should not use the invalid credentials" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_netrc_file_mismatched_host() -> Result<(), Error> { + let username = "user"; + let password = "password"; + let server = start_test_server(username, password).await; + + let mut netrc_file = NamedTempFile::new()?; + writeln!( + netrc_file, + r#"machine example.com login {username} password {password}"#, + )?; + + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_netrc(Some( + Netrc::from_file(netrc_file.path()).expect("Test has valid netrc file"), + )), + ) + .build(); + + assert_eq!( + client.get(server.uri()).send().await?.status(), + 401, + "Credentials should not be pulled from the netrc file due to host mismatch" + ); + + let mut url = Url::parse(&server.uri())?; + url.set_username(username).unwrap(); + url.set_password(Some(password)).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 200, + "Credentials in the URL should still work" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_netrc_file_mismatched_username() -> Result<(), Error> { + let username = "user"; + let password = "password"; + let server = start_test_server(username, password).await; + let base_url = Url::parse(&server.uri())?; + + let mut netrc_file = NamedTempFile::new()?; + writeln!( + netrc_file, + r#"machine {} login {username} password {password}"#, + base_url.host_str().unwrap() + )?; + + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_netrc(Some( + Netrc::from_file(netrc_file.path()).expect("Test has valid netrc file"), + )), + ) + .build(); + + let mut url = base_url.clone(); + url.set_username("other-user").unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "The netrc password should not be used due to a username mismatch" + ); + + let mut url = base_url.clone(); + url.set_username("user").unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 200, + "The netrc password should be used for a matching user" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_keyring() -> Result<(), Error> { + let username = "user"; + let password = "password"; + let server = start_test_server(username, password).await; + let base_url = Url::parse(&server.uri())?; + + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_keyring(Some(KeyringProvider::dummy([( + ( + format!( + "{}:{}", + base_url.host_str().unwrap(), + base_url.port().unwrap() + ), + username, + ), + password, + )]))), + ) + .build(); + + assert_eq!( + client.get(server.uri()).send().await?.status(), + 401, + "Credentials are not pulled from the keyring without a username" + ); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 200, + "Credentials for the username should be pulled from the keyring" + ); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + url.set_password(Some("invalid")).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "Password in the URL should take precedence and fail" + ); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + assert_eq!( + client.get(url.clone()).send().await?.status(), + 200, + "Subsequent requests should not use the invalid password" + ); + + let mut url = base_url.clone(); + url.set_username("other_user").unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "Credentials are not pulled from the keyring when given another username" + ); + + Ok(()) +} + +/// We include ports in keyring requests, e.g., `localhost:8000` should be distinct from `localhost`, +/// unless the server is running on a default port, e.g., `localhost:80` is equivalent to `localhost`. +/// We don't unit test the latter case because it's possible to collide with a server a developer is +/// actually running. +#[test(tokio::test)] +async fn test_keyring_includes_non_standard_port() -> Result<(), Error> { + let username = "user"; + let password = "password"; + let server = start_test_server(username, password).await; + let base_url = Url::parse(&server.uri())?; + + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_keyring(Some(KeyringProvider::dummy([( + // Omit the port from the keyring entry + (base_url.host_str().unwrap(), username), + password, + )]))), + ) + .build(); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 401, + "We should fail because the port is not present in the keyring entry" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_credentials_in_keyring_seed() -> Result<(), Error> { + let username = "user"; + let password = "password"; + + let server = start_test_server(username, password).await; + let base_url = Url::parse(&server.uri())?; + let cache = CredentialsCache::new(); + + // Seed _just_ the username. This cache entry should be ignored and we should + // still find a password via the keyring. + cache.insert( + &base_url, + Arc::new(Credentials::new(Some(username.to_string()), None)), + ); + let client = + test_client_builder() + .with(AuthMiddleware::new().with_cache(cache).with_keyring(Some( + KeyringProvider::dummy([( + ( + format!( + "{}:{}", + base_url.host_str().unwrap(), + base_url.port().unwrap() + ), + username, + ), + password, + )]), + ))) + .build(); + + assert_eq!( + client.get(server.uri()).send().await?.status(), + 401, + "Credentials are not pulled from the keyring without a username" + ); + + let mut url = base_url.clone(); + url.set_username(username).unwrap(); + assert_eq!( + client.get(url).send().await?.status(), + 200, + "Credentials for the username should be pulled from the keyring" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_credentials_in_url_multiple_realms() -> Result<(), Error> { + let username_1 = "user1"; + let password_1 = "password1"; + let server_1 = start_test_server(username_1, password_1).await; + let base_url_1 = Url::parse(&server_1.uri())?; + + let username_2 = "user2"; + let password_2 = "password2"; + let server_2 = start_test_server(username_2, password_2).await; + let base_url_2 = Url::parse(&server_2.uri())?; + + let cache = CredentialsCache::new(); + // Seed the cache with our credentials + cache.insert( + &base_url_1, + Arc::new(Credentials::new( + Some(username_1.to_string()), + Some(password_1.to_string()), + )), + ); + cache.insert( + &base_url_2, + Arc::new(Credentials::new( + Some(username_2.to_string()), + Some(password_2.to_string()), + )), + ); + + let client = test_client_builder() + .with(AuthMiddleware::new().with_cache(cache)) + .build(); + + // Both servers should work + assert_eq!( + client.get(server_1.uri()).send().await?.status(), + 200, + "Requests should not require credentials" + ); + assert_eq!( + client.get(server_2.uri()).send().await?.status(), + 200, + "Requests should not require credentials" + ); + + assert_eq!( + client + .get(format!("{}/foo", server_1.uri())) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same realm" + ); + assert_eq!( + client + .get(format!("{}/foo", server_2.uri())) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same realm" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_credentials_from_keyring_multiple_realms() -> Result<(), Error> { + let username_1 = "user1"; + let password_1 = "password1"; + let server_1 = start_test_server(username_1, password_1).await; + let base_url_1 = Url::parse(&server_1.uri())?; + + let username_2 = "user2"; + let password_2 = "password2"; + let server_2 = start_test_server(username_2, password_2).await; + let base_url_2 = Url::parse(&server_2.uri())?; + + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_keyring(Some(KeyringProvider::dummy([ + ( + ( + format!( + "{}:{}", + base_url_1.host_str().unwrap(), + base_url_1.port().unwrap() + ), + username_1, + ), + password_1, + ), + ( + ( + format!( + "{}:{}", + base_url_2.host_str().unwrap(), + base_url_2.port().unwrap() + ), + username_2, + ), + password_2, + ), + ]))), + ) + .build(); + + // Both servers do not work without a username + assert_eq!( + client.get(server_1.uri()).send().await?.status(), + 401, + "Requests should require a username" + ); + assert_eq!( + client.get(server_2.uri()).send().await?.status(), + 401, + "Requests should require a username" + ); + + let mut url_1 = base_url_1.clone(); + url_1.set_username(username_1).unwrap(); + assert_eq!( + client.get(url_1.clone()).send().await?.status(), + 200, + "Requests with a username should succeed" + ); + assert_eq!( + client.get(server_2.uri()).send().await?.status(), + 401, + "Credentials should not be re-used for the second server" + ); + + let mut url_2 = base_url_2.clone(); + url_2.set_username(username_2).unwrap(); + assert_eq!( + client.get(url_2.clone()).send().await?.status(), + 200, + "Requests with a username should succeed" + ); + + assert_eq!( + client.get(format!("{url_1}/foo")).send().await?.status(), + 200, + "Requests can be to different paths in the same realm" + ); + assert_eq!( + client.get(format!("{url_2}/foo")).send().await?.status(), + 200, + "Requests can be to different paths in the same realm" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_credentials_in_url_mixed_authentication_in_realm() -> Result<(), Error> { + let username_1 = "user1"; + let password_1 = "password1"; + let username_2 = "user2"; + let password_2 = "password2"; + + let server = MockServer::start().await; + + Mock::given(method("GET")) + .and(path_regex("/prefix_1.*")) + .and(basic_auth(username_1, password_1)) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path_regex("/prefix_2.*")) + .and(basic_auth(username_2, password_2)) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + // Create a third, public prefix + // It will throw a 401 if it receives credentials + Mock::given(method("GET")) + .and(path_regex("/prefix_3.*")) + .and(basic_auth(username_1, password_1)) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(path_regex("/prefix_3.*")) + .and(basic_auth(username_2, password_2)) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(path_regex("/prefix_3.*")) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + + let base_url = Url::parse(&server.uri())?; + let base_url_1 = base_url.join("prefix_1")?; + let base_url_2 = base_url.join("prefix_2")?; + let base_url_3 = base_url.join("prefix_3")?; + + let cache = CredentialsCache::new(); + + // Seed the cache with our credentials + cache.insert( + &base_url_1, + Arc::new(Credentials::new( + Some(username_1.to_string()), + Some(password_1.to_string()), + )), + ); + cache.insert( + &base_url_2, + Arc::new(Credentials::new( + Some(username_2.to_string()), + Some(password_2.to_string()), + )), + ); + + let client = test_client_builder() + .with(AuthMiddleware::new().with_cache(cache)) + .build(); + + // Both servers should work + assert_eq!( + client.get(base_url_1.clone()).send().await?.status(), + 200, + "Requests should not require credentials" + ); + assert_eq!( + client.get(base_url_2.clone()).send().await?.status(), + 200, + "Requests should not require credentials" + ); + assert_eq!( + client + .get(base_url.join("prefix_1/foo")?) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same realm" + ); + assert_eq!( + client + .get(base_url.join("prefix_2/foo")?) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same realm" + ); + assert_eq!( + client + .get(base_url.join("prefix_1_foo")?) + .send() + .await? + .status(), + 401, + "Requests to paths with a matching prefix but different resource segments should fail" + ); + + assert_eq!( + client.get(base_url_3.clone()).send().await?.status(), + 200, + "Requests to the 'public' prefix should not use credentials" + ); + + Ok(()) +} + +#[test(tokio::test)] +async fn test_credentials_from_keyring_mixed_authentication_in_realm() -> Result<(), Error> { + let username_1 = "user1"; + let password_1 = "password1"; + let username_2 = "user2"; + let password_2 = "password2"; + + let server = MockServer::start().await; + + Mock::given(method("GET")) + .and(path_regex("/prefix_1.*")) + .and(basic_auth(username_1, password_1)) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path_regex("/prefix_2.*")) + .and(basic_auth(username_2, password_2)) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + // Create a third, public prefix + // It will throw a 401 if it receives credentials + Mock::given(method("GET")) + .and(path_regex("/prefix_3.*")) + .and(basic_auth(username_1, password_1)) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(path_regex("/prefix_3.*")) + .and(basic_auth(username_2, password_2)) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(path_regex("/prefix_3.*")) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + + let base_url = Url::parse(&server.uri())?; + let base_url_1 = base_url.join("prefix_1")?; + let base_url_2 = base_url.join("prefix_2")?; + let base_url_3 = base_url.join("prefix_3")?; + + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_keyring(Some(KeyringProvider::dummy([ + ( + ( + format!( + "{}:{}", + base_url_1.host_str().unwrap(), + base_url_1.port().unwrap() + ), + username_1, + ), + password_1, + ), + ( + ( + format!( + "{}:{}", + base_url_2.host_str().unwrap(), + base_url_2.port().unwrap() + ), + username_2, + ), + password_2, + ), + ]))), + ) + .build(); + + // Both servers do not work without a username + assert_eq!( + client.get(base_url_1.clone()).send().await?.status(), + 401, + "Requests should require a username" + ); + assert_eq!( + client.get(base_url_2.clone()).send().await?.status(), + 401, + "Requests should require a username" + ); + + let mut url_1 = base_url_1.clone(); + url_1.set_username(username_1).unwrap(); + assert_eq!( + client.get(url_1.clone()).send().await?.status(), + 200, + "Requests with a username should succeed" + ); + assert_eq!( + client.get(base_url_2.clone()).send().await?.status(), + 401, + "Credentials should not be re-used for the second prefix" + ); + + let mut url_2 = base_url_2.clone(); + url_2.set_username(username_2).unwrap(); + assert_eq!( + client.get(url_2.clone()).send().await?.status(), + 200, + "Requests with a username should succeed" + ); + + assert_eq!( + client + .get(base_url.join("prefix_1/foo")?) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same prefix" + ); + assert_eq!( + client + .get(base_url.join("prefix_2/foo")?) + .send() + .await? + .status(), + 200, + "Requests can be to different paths in the same prefix" + ); + assert_eq!( + client + .get(base_url.join("prefix_1_foo")?) + .send() + .await? + .status(), + 401, + "Requests to paths with a matching prefix but different resource segments should fail" + ); + assert_eq!( + client.get(base_url_3.clone()).send().await?.status(), + 200, + "Requests to the 'public' prefix should not use credentials" + ); + + Ok(()) +} + +/// Demonstrates "incorrect" behavior in our cache which avoids an expensive fetch of +/// credentials for _every_ request URL at the cost of inconsistent behavior when +/// credentials are not scoped to a realm. +#[test(tokio::test)] +async fn test_credentials_from_keyring_mixed_authentication_in_realm_same_username( +) -> Result<(), Error> { + let username = "user"; + let password_1 = "password1"; + let password_2 = "password2"; + + let server = MockServer::start().await; + + Mock::given(method("GET")) + .and(path_regex("/prefix_1.*")) + .and(basic_auth(username, password_1)) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .and(path_regex("/prefix_2.*")) + .and(basic_auth(username, password_2)) + .respond_with(ResponseTemplate::new(200)) + .mount(&server) + .await; + + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(401)) + .mount(&server) + .await; + + let base_url = Url::parse(&server.uri())?; + let base_url_1 = base_url.join("prefix_1")?; + let base_url_2 = base_url.join("prefix_2")?; + + let client = test_client_builder() + .with( + AuthMiddleware::new() + .with_cache(CredentialsCache::new()) + .with_keyring(Some(KeyringProvider::dummy([ + ((base_url_1.clone(), username), password_1), + ((base_url_2.clone(), username), password_2), + ]))), + ) + .build(); + + // Both servers do not work without a username + assert_eq!( + client.get(base_url_1.clone()).send().await?.status(), + 401, + "Requests should require a username" + ); + assert_eq!( + client.get(base_url_2.clone()).send().await?.status(), + 401, + "Requests should require a username" + ); + + let mut url_1 = base_url_1.clone(); + url_1.set_username(username).unwrap(); + assert_eq!( + client.get(url_1.clone()).send().await?.status(), + 200, + "The first request with a username will succeed" + ); + assert_eq!( + client.get(base_url_2.clone()).send().await?.status(), + 401, + "Credentials should not be re-used for the second prefix" + ); + assert_eq!( + client + .get(base_url.join("prefix_1/foo")?) + .send() + .await? + .status(), + 200, + "Subsequent requests can be to different paths in the same prefix" + ); + + let mut url_2 = base_url_2.clone(); + url_2.set_username(username).unwrap(); + assert_eq!( + client.get(url_2.clone()).send().await?.status(), + 401, // INCORRECT BEHAVIOR + "A request with the same username and realm for a URL that needs a different password will fail" + ); + assert_eq!( + client + .get(base_url.join("prefix_2/foo")?) + .send() + .await? + .status(), + 401, // INCORRECT BEHAVIOR + "Requests to other paths in the failing prefix will also fail" + ); + + Ok(()) +} diff --git a/crates/uv-auth/src/realm.rs b/crates/uv-auth/src/realm.rs index 92d73d5f8..fcda89ade 100644 --- a/crates/uv-auth/src/realm.rs +++ b/crates/uv-auth/src/realm.rs @@ -59,89 +59,4 @@ impl Display for Realm { } #[cfg(test)] -mod tests { - use url::{ParseError, Url}; - - use crate::Realm; - - #[test] - fn test_should_retain_auth() -> Result<(), ParseError> { - // Exact match (https) - assert_eq!( - Realm::from(&Url::parse("https://example.com")?), - Realm::from(&Url::parse("https://example.com")?) - ); - - // Exact match (with port) - assert_eq!( - Realm::from(&Url::parse("https://example.com:1234")?), - Realm::from(&Url::parse("https://example.com:1234")?) - ); - - // Exact match (http) - assert_eq!( - Realm::from(&Url::parse("http://example.com")?), - Realm::from(&Url::parse("http://example.com")?) - ); - - // Okay, path differs - assert_eq!( - Realm::from(&Url::parse("http://example.com/foo")?), - Realm::from(&Url::parse("http://example.com/bar")?) - ); - - // Okay, default port differs (https) - assert_eq!( - Realm::from(&Url::parse("https://example.com:443")?), - Realm::from(&Url::parse("https://example.com")?) - ); - - // Okay, default port differs (http) - assert_eq!( - Realm::from(&Url::parse("http://example.com:80")?), - Realm::from(&Url::parse("http://example.com")?) - ); - - // Mismatched scheme - assert_ne!( - Realm::from(&Url::parse("https://example.com")?), - Realm::from(&Url::parse("http://example.com")?) - ); - - // Mismatched scheme, we explicitly do not allow upgrade to https - assert_ne!( - Realm::from(&Url::parse("http://example.com")?), - Realm::from(&Url::parse("https://example.com")?) - ); - - // Mismatched host - assert_ne!( - Realm::from(&Url::parse("https://foo.com")?), - Realm::from(&Url::parse("https://bar.com")?) - ); - - // Mismatched port - assert_ne!( - Realm::from(&Url::parse("https://example.com:1234")?), - Realm::from(&Url::parse("https://example.com:5678")?) - ); - - // Mismatched port, with one as default for scheme - assert_ne!( - Realm::from(&Url::parse("https://example.com:443")?), - Realm::from(&Url::parse("https://example.com:5678")?) - ); - assert_ne!( - Realm::from(&Url::parse("https://example.com:1234")?), - Realm::from(&Url::parse("https://example.com:443")?) - ); - - // Mismatched port, with default for a different scheme - assert_ne!( - Realm::from(&Url::parse("https://example.com:80")?), - Realm::from(&Url::parse("https://example.com")?) - ); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-auth/src/realm/tests.rs b/crates/uv-auth/src/realm/tests.rs new file mode 100644 index 000000000..753b37c09 --- /dev/null +++ b/crates/uv-auth/src/realm/tests.rs @@ -0,0 +1,84 @@ +use url::{ParseError, Url}; + +use crate::Realm; + +#[test] +fn test_should_retain_auth() -> Result<(), ParseError> { + // Exact match (https) + assert_eq!( + Realm::from(&Url::parse("https://example.com")?), + Realm::from(&Url::parse("https://example.com")?) + ); + + // Exact match (with port) + assert_eq!( + Realm::from(&Url::parse("https://example.com:1234")?), + Realm::from(&Url::parse("https://example.com:1234")?) + ); + + // Exact match (http) + assert_eq!( + Realm::from(&Url::parse("http://example.com")?), + Realm::from(&Url::parse("http://example.com")?) + ); + + // Okay, path differs + assert_eq!( + Realm::from(&Url::parse("http://example.com/foo")?), + Realm::from(&Url::parse("http://example.com/bar")?) + ); + + // Okay, default port differs (https) + assert_eq!( + Realm::from(&Url::parse("https://example.com:443")?), + Realm::from(&Url::parse("https://example.com")?) + ); + + // Okay, default port differs (http) + assert_eq!( + Realm::from(&Url::parse("http://example.com:80")?), + Realm::from(&Url::parse("http://example.com")?) + ); + + // Mismatched scheme + assert_ne!( + Realm::from(&Url::parse("https://example.com")?), + Realm::from(&Url::parse("http://example.com")?) + ); + + // Mismatched scheme, we explicitly do not allow upgrade to https + assert_ne!( + Realm::from(&Url::parse("http://example.com")?), + Realm::from(&Url::parse("https://example.com")?) + ); + + // Mismatched host + assert_ne!( + Realm::from(&Url::parse("https://foo.com")?), + Realm::from(&Url::parse("https://bar.com")?) + ); + + // Mismatched port + assert_ne!( + Realm::from(&Url::parse("https://example.com:1234")?), + Realm::from(&Url::parse("https://example.com:5678")?) + ); + + // Mismatched port, with one as default for scheme + assert_ne!( + Realm::from(&Url::parse("https://example.com:443")?), + Realm::from(&Url::parse("https://example.com:5678")?) + ); + assert_ne!( + Realm::from(&Url::parse("https://example.com:1234")?), + Realm::from(&Url::parse("https://example.com:443")?) + ); + + // Mismatched port, with default for a different scheme + assert_ne!( + Realm::from(&Url::parse("https://example.com:80")?), + Realm::from(&Url::parse("https://example.com")?) + ); + + Ok(()) +} diff --git a/crates/uv-bench/Cargo.toml b/crates/uv-bench/Cargo.toml index 7f3ba2323..a46f3122a 100644 --- a/crates/uv-bench/Cargo.toml +++ b/crates/uv-bench/Cargo.toml @@ -15,6 +15,7 @@ license = { workspace = true } workspace = true [lib] +doctest = false bench = false [[bench]] diff --git a/crates/uv-bench/benches/uv.rs b/crates/uv-bench/benches/uv.rs index 3fee93a73..8798d1de2 100644 --- a/crates/uv-bench/benches/uv.rs +++ b/crates/uv-bench/benches/uv.rs @@ -86,7 +86,8 @@ mod resolver { use uv_cache::Cache; use uv_client::RegistryClient; use uv_configuration::{ - BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy, SourceStrategy, + BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy, LowerBound, + SourceStrategy, }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; @@ -191,6 +192,7 @@ mod resolver { &build_options, &hashes, exclude_newer, + LowerBound::default(), sources, concurrency, ); diff --git a/crates/uv-build-backend/Cargo.toml b/crates/uv-build-backend/Cargo.toml new file mode 100644 index 000000000..a6fa3d51c --- /dev/null +++ b/crates/uv-build-backend/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "uv-build-backend" +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 + +[lib] +doctest = false + +[dependencies] +uv-distribution-filename = { workspace = true } +uv-fs = { workspace = true } +uv-normalize = { workspace = true } +uv-pep440 = { workspace = true } +uv-pep508 = { workspace = true } +uv-pubgrub = { workspace = true } +uv-pypi-types = { workspace = true } +uv-warnings = { workspace = true } + +csv = { workspace = true} +fs-err = { workspace = true } +glob = { workspace = true } +itertools = { workspace = true } +serde = { workspace = true } +sha2 = { workspace = true } +spdx = { workspace = true } +thiserror = { workspace = true } +toml = { workspace = true } +tracing = { workspace = true } +walkdir = { workspace = true } +zip = { workspace = true } + +[lints] +workspace = true + +[dev-dependencies] +indoc = { version = "2.0.5" } +insta = { version = "1.40.0" } +tempfile = { version = "3.12.0" } diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs new file mode 100644 index 000000000..37915a702 --- /dev/null +++ b/crates/uv-build-backend/src/lib.rs @@ -0,0 +1,517 @@ +mod metadata; +mod pep639_glob; + +use crate::metadata::{PyProjectToml, ValidationError}; +use crate::pep639_glob::Pep639GlobError; +use fs_err::File; +use glob::{GlobError, PatternError}; +use itertools::Itertools; +use sha2::{Digest, Sha256}; +use std::fs::FileType; +use std::io::{BufReader, Read, Write}; +use std::path::{Path, PathBuf, StripPrefixError}; +use std::{io, mem}; +use thiserror::Error; +use tracing::{debug, trace}; +use uv_distribution_filename::WheelFilename; +use uv_fs::Simplified; +use walkdir::WalkDir; +use zip::{CompressionMethod, ZipWriter}; + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] io::Error), + #[error("Invalid pyproject.toml")] + Toml(#[from] toml::de::Error), + #[error("Invalid pyproject.toml")] + Validation(#[from] ValidationError), + #[error("Invalid `project.license-files` glob expression: `{0}`")] + Pep639Glob(String, #[source] Pep639GlobError), + #[error("The `project.license-files` entry is not a valid glob pattern: `{0}`")] + Pattern(String, #[source] PatternError), + /// [`GlobError`] is a wrapped io error. + #[error(transparent)] + Glob(#[from] GlobError), + #[error("Failed to walk source tree: `{}`", root.user_display())] + WalkDir { + root: PathBuf, + #[source] + err: walkdir::Error, + }, + #[error("Non-UTF-8 paths are not supported: `{}`", _0.user_display())] + NotUtf8Path(PathBuf), + #[error("Failed to walk source tree")] + StripPrefix(#[from] StripPrefixError), + #[error("Unsupported file type: {0:?}")] + UnsupportedFileType(FileType), + #[error("Failed to write wheel zip archive")] + Zip(#[from] zip::result::ZipError), + #[error("Failed to write RECORD file")] + Csv(#[from] csv::Error), + #[error("Expected a Python module with an `__init__.py` at: `{}`", _0.user_display())] + MissingModule(PathBuf), + #[error("Inconsistent metadata between prepare and build step: `{0}`")] + InconsistentSteps(&'static str), +} + +/// Allow dispatching between writing to a directory, writing to zip and writing to a `.tar.gz`. +/// +/// All paths are string types instead of path types since wheel are portable between platforms. +/// +/// Contract: You must call close before dropping to obtain a valid output (dropping is fine in the +/// error case). +trait DirectoryWriter { + /// Add a file with the given content. + fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error>; + + /// Add a file with the given name and return a writer for it. + fn new_writer<'slf>(&'slf mut self, path: &str) -> Result, Error>; + + /// Add a local file. + fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error>; + + /// Create a directory. + fn write_directory(&mut self, directory: &str) -> Result<(), Error>; + + /// Write the `RECORD` file and if applicable, the central directory. + fn close(self, dist_info_dir: &str) -> Result<(), Error>; +} + +/// Zip archive (wheel) writer. +struct ZipDirectoryWriter { + writer: ZipWriter, + compression: CompressionMethod, + /// The entries in the `RECORD` file. + record: Vec, +} + +impl ZipDirectoryWriter { + /// A wheel writer with deflate compression. + fn new_wheel(file: File) -> Self { + Self { + writer: ZipWriter::new(file), + compression: CompressionMethod::Deflated, + record: Vec::new(), + } + } + + /// A wheel writer with no (stored) compression. + /// + /// Since editables are temporary, we save time be skipping compression and decompression. + #[expect(dead_code)] + fn new_editable(file: File) -> Self { + Self { + writer: ZipWriter::new(file), + compression: CompressionMethod::Stored, + record: Vec::new(), + } + } +} + +impl DirectoryWriter for ZipDirectoryWriter { + fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> { + trace!("Adding {}", path); + let options = zip::write::FileOptions::default().compression_method(self.compression); + self.writer.start_file(path, options)?; + self.writer.write_all(bytes)?; + + let hash = format!("{:x}", Sha256::new().chain_update(bytes).finalize()); + self.record.push(RecordEntry { + path: path.to_string(), + hash, + size: bytes.len(), + }); + + Ok(()) + } + + fn new_writer<'slf>(&'slf mut self, path: &str) -> Result, Error> { + // TODO(konsti): We need to preserve permissions, at least the executable bit. + self.writer.start_file( + path, + zip::write::FileOptions::default().compression_method(self.compression), + )?; + Ok(Box::new(&mut self.writer)) + } + + fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error> { + trace!("Adding {} from {}", path, file.user_display()); + let mut reader = BufReader::new(File::open(file)?); + let mut writer = self.new_writer(path)?; + let record = write_hashed(path, &mut reader, &mut writer)?; + drop(writer); + self.record.push(record); + Ok(()) + } + + fn write_directory(&mut self, directory: &str) -> Result<(), Error> { + trace!("Adding directory {}", directory); + let options = zip::write::FileOptions::default().compression_method(self.compression); + Ok(self.writer.add_directory(directory, options)?) + } + + /// Write the `RECORD` file and the central directory. + fn close(mut self, dist_info_dir: &str) -> Result<(), Error> { + let record_path = format!("{dist_info_dir}/RECORD"); + trace!("Adding {record_path}"); + let record = mem::take(&mut self.record); + write_record(&mut self.new_writer(&record_path)?, dist_info_dir, record)?; + + trace!("Adding central directory"); + self.writer.finish()?; + Ok(()) + } +} + +struct FilesystemWrite { + /// The virtualenv or metadata directory that add file paths are relative to. + root: PathBuf, + /// The entries in the `RECORD` file. + record: Vec, +} + +impl FilesystemWrite { + fn new(root: &Path) -> Self { + Self { + root: root.to_owned(), + record: Vec::new(), + } + } +} + +/// File system writer. +impl DirectoryWriter for FilesystemWrite { + fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> { + trace!("Adding {}", path); + let hash = format!("{:x}", Sha256::new().chain_update(bytes).finalize()); + self.record.push(RecordEntry { + path: path.to_string(), + hash, + size: bytes.len(), + }); + + Ok(fs_err::write(self.root.join(path), bytes)?) + } + + fn new_writer<'slf>(&'slf mut self, path: &str) -> Result, Error> { + trace!("Adding {}", path); + Ok(Box::new(File::create(self.root.join(path))?)) + } + + fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error> { + trace!("Adding {} from {}", path, file.user_display()); + let mut reader = BufReader::new(File::open(file)?); + let mut writer = self.new_writer(path)?; + let record = write_hashed(path, &mut reader, &mut writer)?; + drop(writer); + self.record.push(record); + Ok(()) + } + + fn write_directory(&mut self, directory: &str) -> Result<(), Error> { + trace!("Adding directory {}", directory); + Ok(fs_err::create_dir(self.root.join(directory))?) + } + + /// Write the `RECORD` file. + fn close(mut self, dist_info_dir: &str) -> Result<(), Error> { + let record = mem::take(&mut self.record); + write_record( + &mut self.new_writer(&format!("{dist_info_dir}/RECORD"))?, + dist_info_dir, + record, + )?; + + Ok(()) + } +} + +/// An entry in the `RECORD` file. +/// +/// +struct RecordEntry { + /// The path to the file relative to the package root. + /// + /// While the spec would allow backslashes, we always use portable paths with forward slashes. + path: String, + /// The SHA256 of the files. + hash: String, + /// The size of the file in bytes. + size: usize, +} + +/// Read the input file and write it both to the hasher and the target file. +/// +/// We're implementing this tee-ing manually since there is no sync `InspectReader` or std tee +/// function. +fn write_hashed( + path: &str, + reader: &mut dyn Read, + writer: &mut dyn Write, +) -> Result { + let mut hasher = Sha256::new(); + let mut size = 0; + // 8KB is the default defined in `std::sys_common::io`. + let mut buffer = vec![0; 8 * 1024]; + loop { + let read = match reader.read(&mut buffer) { + Ok(read) => read, + Err(err) if err.kind() == io::ErrorKind::Interrupted => continue, + Err(err) => return Err(err), + }; + if read == 0 { + // End of file + break; + } + hasher.update(&buffer[..read]); + writer.write_all(&buffer[..read])?; + size += read; + } + Ok(RecordEntry { + path: path.to_string(), + hash: format!("{:x}", hasher.finalize()), + size, + }) +} + +/// Build a wheel from the source tree and place it in the output directory. +pub fn build( + source_tree: &Path, + wheel_dir: &Path, + metadata_directory: Option<&Path>, + uv_version: &str, +) -> Result { + let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?; + let pyproject_toml = PyProjectToml::parse(&contents)?; + pyproject_toml.check_build_system("1.0.0+test"); + + check_metadata_directory(source_tree, metadata_directory, &pyproject_toml)?; + + let filename = WheelFilename { + name: pyproject_toml.name().clone(), + version: pyproject_toml.version().clone(), + build_tag: None, + python_tag: vec!["py3".to_string()], + abi_tag: vec!["none".to_string()], + platform_tag: vec!["any".to_string()], + }; + + let wheel_path = wheel_dir.join(filename.to_string()); + debug!("Writing wheel at {}", wheel_path.user_display()); + let mut wheel_writer = ZipDirectoryWriter::new_wheel(File::create(&wheel_path)?); + + debug!("Adding content files to {}", wheel_path.user_display()); + let strip_root = source_tree.join("src"); + let module_root = strip_root.join(pyproject_toml.name().as_dist_info_name().as_ref()); + if !module_root.join("__init__.py").is_file() { + return Err(Error::MissingModule(module_root)); + } + for entry in WalkDir::new(module_root) { + let entry = entry.map_err(|err| Error::WalkDir { + root: source_tree.to_path_buf(), + err, + })?; + + let relative_path = entry.path().strip_prefix(&strip_root)?; + let relative_path_str = relative_path + .to_str() + .ok_or_else(|| Error::NotUtf8Path(relative_path.to_path_buf()))?; + if entry.file_type().is_dir() { + wheel_writer.write_directory(relative_path_str)?; + } else if entry.file_type().is_file() { + wheel_writer.write_file(relative_path_str, entry.path())?; + } else { + // TODO(konsti): We may want to support symlinks, there is support for installing them. + return Err(Error::UnsupportedFileType(entry.file_type())); + } + + entry.path(); + } + + debug!("Adding metadata files to {}", wheel_path.user_display()); + let dist_info_dir = write_dist_info( + &mut wheel_writer, + &pyproject_toml, + &filename, + source_tree, + uv_version, + )?; + wheel_writer.close(&dist_info_dir)?; + + Ok(filename) +} + +/// Write the dist-info directory to the output directory without building the wheel. +pub fn metadata( + source_tree: &Path, + metadata_directory: &Path, + uv_version: &str, +) -> Result { + let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?; + let pyproject_toml = PyProjectToml::parse(&contents)?; + pyproject_toml.check_build_system("1.0.0+test"); + + let filename = WheelFilename { + name: pyproject_toml.name().clone(), + version: pyproject_toml.version().clone(), + build_tag: None, + python_tag: vec!["py3".to_string()], + abi_tag: vec!["none".to_string()], + platform_tag: vec!["any".to_string()], + }; + + debug!( + "Writing metadata files to {}", + metadata_directory.user_display() + ); + let mut wheel_writer = FilesystemWrite::new(metadata_directory); + let dist_info_dir = write_dist_info( + &mut wheel_writer, + &pyproject_toml, + &filename, + source_tree, + uv_version, + )?; + wheel_writer.close(&dist_info_dir)?; + + Ok(dist_info_dir) +} + +/// PEP 517 requires that the metadata directory from the prepare metadata call is identical to the +/// build wheel call. This method performs a prudence check that `METADATA` and `entry_points.txt` +/// match. +fn check_metadata_directory( + source_tree: &Path, + metadata_directory: Option<&Path>, + pyproject_toml: &PyProjectToml, +) -> Result<(), Error> { + let Some(metadata_directory) = metadata_directory else { + return Ok(()); + }; + + let dist_info_dir = format!( + "{}-{}.dist-info", + pyproject_toml.name().as_dist_info_name(), + pyproject_toml.version() + ); + + // `METADATA` is a mandatory file. + let current = pyproject_toml + .to_metadata(source_tree)? + .core_metadata_format(); + let previous = + fs_err::read_to_string(metadata_directory.join(&dist_info_dir).join("METADATA"))?; + if previous != current { + return Err(Error::InconsistentSteps("METADATA")); + } + + // `entry_points.txt` is not written if it would be empty. + let entrypoints_path = metadata_directory + .join(&dist_info_dir) + .join("entry_points.txt"); + match pyproject_toml.to_entry_points()? { + None => { + if entrypoints_path.is_file() { + return Err(Error::InconsistentSteps("entry_points.txt")); + } + } + Some(entrypoints) => { + if fs_err::read_to_string(&entrypoints_path)? != entrypoints { + return Err(Error::InconsistentSteps("entry_points.txt")); + } + } + } + + Ok(()) +} + +/// Add `METADATA` and `entry_points.txt` to the dist-info directory. +/// +/// Returns the name of the dist-info directory. +fn write_dist_info( + writer: &mut dyn DirectoryWriter, + pyproject_toml: &PyProjectToml, + filename: &WheelFilename, + root: &Path, + uv_version: &str, +) -> Result { + let dist_info_dir = format!( + "{}-{}.dist-info", + pyproject_toml.name().as_dist_info_name(), + pyproject_toml.version() + ); + + writer.write_directory(&dist_info_dir)?; + + // Add `WHEEL`. + let wheel_info = wheel_info(filename, uv_version); + writer.write_bytes(&format!("{dist_info_dir}/WHEEL"), wheel_info.as_bytes())?; + + // Add `entry_points.txt`. + if let Some(entrypoint) = pyproject_toml.to_entry_points()? { + writer.write_bytes( + &format!("{dist_info_dir}/entry_points.txt"), + entrypoint.as_bytes(), + )?; + } + + // Add `METADATA`. + let metadata = pyproject_toml.to_metadata(root)?.core_metadata_format(); + writer.write_bytes(&format!("{dist_info_dir}/METADATA"), metadata.as_bytes())?; + + // `RECORD` is added on closing. + + Ok(dist_info_dir) +} + +/// Returns the `WHEEL` file contents. +fn wheel_info(filename: &WheelFilename, uv_version: &str) -> String { + // https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-contents + let mut wheel_info = vec![ + ("Wheel-Version", "1.0".to_string()), + ("Generator", format!("uv {uv_version}")), + ("Root-Is-Purelib", "true".to_string()), + ]; + for python_tag in &filename.python_tag { + for abi_tag in &filename.abi_tag { + for platform_tag in &filename.platform_tag { + wheel_info.push(("Tag", format!("{python_tag}-{abi_tag}-{platform_tag}"))); + } + } + } + wheel_info + .into_iter() + .map(|(key, value)| format!("{key}: {value}")) + .join("\n") +} + +/// Write the `RECORD` file. +/// +/// +fn write_record( + writer: &mut dyn Write, + dist_info_dir: &str, + record: Vec, +) -> Result<(), Error> { + let mut record_writer = csv::Writer::from_writer(writer); + for entry in record { + record_writer.write_record(&[ + entry.path, + format!("sha256={}", entry.hash), + entry.size.to_string(), + ])?; + } + + // We can't compute the hash or size for RECORD without modifying it at the same time. + record_writer.write_record(&[ + format!("{dist_info_dir}/RECORD"), + String::new(), + String::new(), + ])?; + record_writer.flush()?; + Ok(()) +} + +#[cfg(test)] +mod tests; diff --git a/crates/uv-build-backend/src/metadata.rs b/crates/uv-build-backend/src/metadata.rs new file mode 100644 index 000000000..e825a5551 --- /dev/null +++ b/crates/uv-build-backend/src/metadata.rs @@ -0,0 +1,631 @@ +use crate::pep639_glob::parse_pep639_glob; +use crate::Error; +use itertools::Itertools; +use serde::Deserialize; +use std::collections::{BTreeMap, Bound}; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use tracing::debug; +use uv_fs::Simplified; +use uv_normalize::{ExtraName, PackageName}; +use uv_pep440::{Version, VersionSpecifiers}; +use uv_pep508::{Requirement, VersionOrUrl}; +use uv_pubgrub::PubGrubSpecifier; +use uv_pypi_types::{Metadata23, VerbatimParsedUrl}; +use uv_warnings::warn_user_once; + +#[derive(Debug, Error)] +pub enum ValidationError { + /// The spec isn't clear about what the values in that field would be, and we only support the + /// default value (UTF-8). + #[error("Charsets other than UTF-8 are not supported. Please convert your README to UTF-8 and remove `project.readme.charset`.")] + ReadmeCharset, + #[error("Unknown Readme extension `{0}`, can't determine content type. Please use a support extension (`.md`, `.rst`, `.txt`) or set the content type manually.")] + UnknownExtension(String), + #[error("Can't infer content type because `{}` does not have an extension. Please use a support extension (`.md`, `.rst`, `.txt`) or set the content type manually.", _0.user_display())] + MissingExtension(PathBuf), + #[error("Unsupported content type: `{0}`")] + UnsupportedContentType(String), + #[error("`project.description` must be a single line")] + DescriptionNewlines, + #[error("Dynamic metadata is not supported")] + Dynamic, + #[error("When `project.license-files` is defined, `project.license` must be an SPDX expression string")] + MixedLicenseGenerations, + #[error("Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `{0}`")] + InvalidGroup(String), + #[error( + "Entrypoint names must consist of letters, numbers, dots and dashes; invalid name: `{0}`" + )] + InvalidName(String), + #[error("Use `project.scripts` instead of `project.entry-points.console_scripts`")] + ReservedScripts, + #[error("Use `project.gui-scripts` instead of `project.entry-points.gui_scripts`")] + ReservedGuiScripts, + #[error("`project.license` is not a valid SPDX expression: `{0}`")] + InvalidSpdx(String, #[source] spdx::error::ParseError), +} + +/// A `pyproject.toml` as specified in PEP 517. +#[derive(Deserialize, Debug, Clone)] +#[serde( + rename_all = "kebab-case", + expecting = "The project table needs to follow \ + https://packaging.python.org/en/latest/guides/writing-pyproject-toml" +)] +pub(crate) struct PyProjectToml { + /// Project metadata + project: Project, + /// Build-related data + build_system: BuildSystem, +} + +impl PyProjectToml { + pub(crate) fn name(&self) -> &PackageName { + &self.project.name + } + + pub(crate) fn version(&self) -> &Version { + &self.project.version + } + + pub(crate) fn parse(contents: &str) -> Result { + Ok(toml::from_str(contents)?) + } + + /// Warn if the `[build-system]` table looks suspicious. + /// + /// Example of a valid table: + /// + /// ```toml + /// [build-system] + /// requires = ["uv>=0.4.15,<5"] + /// build-backend = "uv" + /// ``` + /// + /// Returns whether all checks passed. + pub(crate) fn check_build_system(&self, uv_version: &str) -> bool { + let mut passed = true; + if self.build_system.build_backend.as_deref() != Some("uv") { + warn_user_once!( + r#"The value for `build_system.build-backend` should be `"uv"`, not `"{}"`"#, + self.build_system.build_backend.clone().unwrap_or_default() + ); + passed = false; + } + + let uv_version = + Version::from_str(uv_version).expect("uv's own version is not PEP 440 compliant"); + let next_minor = uv_version.release().get(1).copied().unwrap_or_default() + 1; + let next_breaking = Version::new([0, next_minor]); + + let expected = || { + format!( + "Expected a single uv requirement in `build-system.requires`, found `{}`", + toml::to_string(&self.build_system.requires).unwrap_or_default() + ) + }; + + let [uv_requirement] = &self.build_system.requires.as_slice() else { + warn_user_once!("{}", expected()); + return false; + }; + if uv_requirement.name.as_str() != "uv" { + warn_user_once!("{}", expected()); + return false; + } + let bounded = match &uv_requirement.version_or_url { + None => false, + Some(VersionOrUrl::Url(_)) => { + // We can't validate the url + true + } + Some(VersionOrUrl::VersionSpecifier(specifier)) => { + // We don't check how wide the range is (that's up to the user), we just + // check that the current version is compliant, to avoid accidentally using a + // too new or too old uv, and we check that an upper bound exists. The latter + // is very important to allow making breaking changes in uv without breaking + // the existing immutable source distributions on pypi. + if !specifier.contains(&uv_version) { + // This is allowed to happen when testing prereleases, but we should still warn. + warn_user_once!( + r#"`build_system.requires = ["{uv_requirement}"]` does not contain the + current uv version {uv_version}"#, + ); + passed = false; + } + PubGrubSpecifier::from_pep440_specifiers(specifier) + .ok() + .and_then(|specifier| Some(specifier.bounding_range()?.1 != Bound::Unbounded)) + .unwrap_or(false) + } + }; + + if !bounded { + warn_user_once!( + r#"`build_system.requires = ["{uv_requirement}"]` is missing an + upper bound on the uv version such as `<{next_breaking}`. + Without bounding the uv version, the source distribution will break + when a future, breaking version of uv is released."#, + ); + passed = false; + } + + passed + } + + /// Validate and convert a `pyproject.toml` to core metadata. + /// + /// + /// + /// + pub(crate) fn to_metadata(&self, root: &Path) -> Result { + let summary = if let Some(description) = &self.project.description { + if description.contains('\n') { + return Err(ValidationError::DescriptionNewlines.into()); + } + Some(description.clone()) + } else { + None + }; + + let supported_content_types = ["text/plain", "text/x-rst", "text/markdown"]; + let (description, description_content_type) = match &self.project.readme { + Some(Readme::String(path)) => { + let content = fs_err::read_to_string(root.join(path))?; + let content_type = match path.extension().and_then(OsStr::to_str) { + Some("txt") => "text/plain", + Some("rst") => "text/x-rst", + Some("md") => "text/markdown", + Some(unknown) => { + return Err(ValidationError::UnknownExtension(unknown.to_owned()).into()) + } + None => return Err(ValidationError::MissingExtension(path.clone()).into()), + } + .to_string(); + (Some(content), Some(content_type)) + } + Some(Readme::File { + file, + content_type, + charset, + }) => { + let content = fs_err::read_to_string(root.join(file))?; + if !supported_content_types.contains(&content_type.as_str()) { + return Err( + ValidationError::UnsupportedContentType(content_type.clone()).into(), + ); + } + if charset.as_ref().is_some_and(|charset| charset != "UTF-8") { + return Err(ValidationError::ReadmeCharset.into()); + } + (Some(content), Some(content_type.clone())) + } + Some(Readme::Text { + text, + content_type, + charset, + }) => { + if !supported_content_types.contains(&content_type.as_str()) { + return Err( + ValidationError::UnsupportedContentType(content_type.clone()).into(), + ); + } + if charset.as_ref().is_some_and(|charset| charset != "UTF-8") { + return Err(ValidationError::ReadmeCharset.into()); + } + (Some(text.clone()), Some(content_type.clone())) + } + None => (None, None), + }; + + if self + .project + .dynamic + .as_ref() + .is_some_and(|dynamic| !dynamic.is_empty()) + { + return Err(ValidationError::Dynamic.into()); + } + + let author = self + .project + .authors + .as_ref() + .map(|authors| { + authors + .iter() + .filter_map(|author| match author { + Contact::Name { name } => Some(name), + Contact::Email { .. } => None, + Contact::NameEmail { name, .. } => Some(name), + }) + .join(", ") + }) + .filter(|author| !author.is_empty()); + let author_email = self + .project + .authors + .as_ref() + .map(|authors| { + authors + .iter() + .filter_map(|author| match author { + Contact::Name { .. } => None, + Contact::Email { email } => Some(email.clone()), + Contact::NameEmail { name, email } => Some(format!("{name} <{email}>")), + }) + .join(", ") + }) + .filter(|author_email| !author_email.is_empty()); + let maintainer = self + .project + .maintainers + .as_ref() + .map(|maintainers| { + maintainers + .iter() + .filter_map(|maintainer| match maintainer { + Contact::Name { name } => Some(name), + Contact::Email { .. } => None, + Contact::NameEmail { name, .. } => Some(name), + }) + .join(", ") + }) + .filter(|maintainer| !maintainer.is_empty()); + let maintainer_email = self + .project + .maintainers + .as_ref() + .map(|maintainers| { + maintainers + .iter() + .filter_map(|maintainer| match maintainer { + Contact::Name { .. } => None, + Contact::Email { email } => Some(email.clone()), + Contact::NameEmail { name, email } => Some(format!("{name} <{email}>")), + }) + .join(", ") + }) + .filter(|maintainer_email| !maintainer_email.is_empty()); + + // Using PEP 639 bumps the METADATA version + let metadata_version = if self.project.license_files.is_some() + || matches!(self.project.license, Some(License::Spdx(_))) + { + debug!("Found PEP 639 license declarations, using METADATA 2.4"); + "2.4" + } else { + "2.3" + }; + + // TODO(konsti): Issue a warning on old license metadata once PEP 639 is universal. + let (license, license_expression, license_files) = + if let Some(license_globs) = &self.project.license_files { + let license_expression = match &self.project.license { + None => None, + Some(License::Spdx(license_expression)) => Some(license_expression.clone()), + Some(License::Text { .. } | License::File { .. }) => { + return Err(ValidationError::MixedLicenseGenerations.into()) + } + }; + + let mut license_files = Vec::new(); + for license_glob in license_globs { + let pep639_glob = parse_pep639_glob(license_glob) + .map_err(|err| Error::Pep639Glob(license_glob.to_string(), err))?; + let absolute_glob = PathBuf::from(glob::Pattern::escape( + root.simplified().to_string_lossy().as_ref(), + )) + .join(pep639_glob.to_string()) + .to_string_lossy() + .to_string(); + for license_file in glob::glob(&absolute_glob) + .map_err(|err| Error::Pattern(absolute_glob.to_string(), err))? + { + let license_file = license_file + .map_err(Error::Glob)? + .to_string_lossy() + .to_string(); + if !license_files.contains(&license_file) { + license_files.push(license_file); + } + } + } + // The glob order may be unstable + license_files.sort(); + + (None, license_expression, license_files) + } else { + match &self.project.license { + None => (None, None, Vec::new()), + Some(License::Spdx(license_expression)) => { + (None, Some(license_expression.clone()), Vec::new()) + } + Some(License::Text { text }) => (Some(text.clone()), None, Vec::new()), + Some(License::File { file }) => { + let text = fs_err::read_to_string(root.join(file))?; + (Some(text), None, Vec::new()) + } + } + }; + + // Check that the license expression is a valid SPDX identifier. + if let Some(license_expression) = &license_expression { + if let Err(err) = spdx::Expression::parse(license_expression) { + return Err(ValidationError::InvalidSpdx(license_expression.clone(), err).into()); + } + } + + // TODO(konsti): https://peps.python.org/pep-0753/#label-normalization (Draft) + let project_urls = self + .project + .urls + .iter() + .flatten() + .map(|(key, value)| format!("{key}, {value}")) + .collect(); + + let extras = self + .project + .optional_dependencies + .iter() + .flat_map(|optional_dependencies| optional_dependencies.keys()) + .map(ToString::to_string) + .collect(); + + Ok(Metadata23 { + metadata_version: metadata_version.to_string(), + name: self.project.name.to_string(), + version: self.project.version.to_string(), + // Not supported. + platforms: vec![], + // Not supported. + supported_platforms: vec![], + summary, + description, + description_content_type, + keywords: self + .project + .keywords + .as_ref() + .map(|keywords| keywords.join(",")), + home_page: None, + download_url: None, + author, + author_email, + maintainer, + maintainer_email, + license, + license_expression, + license_files, + classifiers: self.project.classifiers.clone().unwrap_or_default(), + requires_dist: self + .project + .dependencies + .iter() + .flatten() + .map(ToString::to_string) + .collect(), + // Not commonly set. + provides_dist: vec![], + // Not supported. + obsoletes_dist: vec![], + requires_python: self + .project + .requires_python + .as_ref() + .map(ToString::to_string), + // Not used by other tools, not supported. + requires_external: vec![], + project_urls, + provides_extras: extras, + dynamic: vec![], + }) + } + + /// Validate and convert the entrypoints in `pyproject.toml`, including console and GUI scripts, + /// to an `entry_points.txt`. + /// + /// + /// + /// Returns `None` if no entrypoints were defined. + pub(crate) fn to_entry_points(&self) -> Result, ValidationError> { + let mut writer = String::new(); + + if self.project.scripts.is_none() + && self.project.gui_scripts.is_none() + && self.project.entry_points.is_none() + { + return Ok(None); + } + + if let Some(scripts) = &self.project.scripts { + Self::write_group(&mut writer, "console_scripts", scripts)?; + } + if let Some(gui_scripts) = &self.project.gui_scripts { + Self::write_group(&mut writer, "gui_scripts", gui_scripts)?; + } + for (group, entries) in self.project.entry_points.iter().flatten() { + if group == "console_scripts" { + return Err(ValidationError::ReservedScripts); + } + if group == "gui_scripts" { + return Err(ValidationError::ReservedGuiScripts); + } + Self::write_group(&mut writer, group, entries)?; + } + Ok(Some(writer)) + } + + /// Write a group to `entry_points.txt`. + fn write_group<'a>( + writer: &mut String, + group: &str, + entries: impl IntoIterator, + ) -> Result<(), ValidationError> { + if !group + .chars() + .next() + .map(|c| c.is_alphanumeric() || c == '_') + .unwrap_or(false) + || !group + .chars() + .all(|c| c.is_alphanumeric() || c == '.' || c == '_') + { + return Err(ValidationError::InvalidGroup(group.to_string())); + } + + writer.push_str(&format!("[{group}]\n")); + for (name, object_reference) in entries { + // More strict than the spec, we enforce the recommendation + if !name + .chars() + .all(|c| c.is_alphanumeric() || c == '.' || c == '-') + { + return Err(ValidationError::InvalidName(name.to_string())); + } + + // TODO(konsti): Validate that the object references are valid Python identifiers. + writer.push_str(&format!("{name} = {object_reference}\n")); + } + writer.push('\n'); + Ok(()) + } +} + +/// The `[project]` section of a pyproject.toml as specified in +/// . +/// +/// This struct does not have schema export; the schema is shared between all Python tools, and we +/// should update the shared schema instead. +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +struct Project { + /// The name of the project. + name: PackageName, + /// The version of the project. + version: Version, + /// The summary description of the project in one line. + description: Option, + /// The full description of the project (i.e. the README). + readme: Option, + /// The Python version requirements of the project. + requires_python: Option, + /// The license under which the project is distributed. + /// + /// Supports both the current standard and the provisional PEP 639. + license: Option, + /// The paths to files containing licenses and other legal notices to be distributed with the + /// project. + /// + /// From the provisional PEP 639 + license_files: Option>, + /// The people or organizations considered to be the "authors" of the project. + authors: Option>, + /// The people or organizations considered to be the "maintainers" of the project. + maintainers: Option>, + /// The keywords for the project. + keywords: Option>, + /// Trove classifiers which apply to the project. + classifiers: Option>, + /// A table of URLs where the key is the URL label and the value is the URL itself. + /// + /// PyPI shows all URLs with their name. For some known patterns, they add favicons. + /// main: + /// archived: + urls: Option>, + /// The console entrypoints of the project. + /// + /// The key of the table is the name of the entry point and the value is the object reference. + scripts: Option>, + /// The GUI entrypoints of the project. + /// + /// The key of the table is the name of the entry point and the value is the object reference. + gui_scripts: Option>, + /// Entrypoints groups of the project. + /// + /// The key of the table is the name of the entry point and the value is the object reference. + entry_points: Option>>, + /// The dependencies of the project. + dependencies: Option>, + /// The optional dependencies of the project. + optional_dependencies: Option>>, + /// Specifies which fields listed by PEP 621 were intentionally unspecified so another tool + /// can/will provide such metadata dynamically. + /// + /// Not supported, an error if anything but the default empty list. + dynamic: Option>, +} + +/// The optional `project.readme` key in a pyproject.toml as specified in +/// . +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged, rename_all = "kebab-case")] +enum Readme { + /// Relative path to the README. + String(PathBuf), + /// Relative path to the README. + File { + file: PathBuf, + content_type: String, + charset: Option, + }, + /// The full description of the project as inline value. + Text { + text: String, + content_type: String, + charset: Option, + }, +} + +/// The optional `project.license` key in a pyproject.toml as specified in +/// . +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +enum License { + /// An SPDX Expression. + /// + /// From the provisional PEP 639. + Spdx(String), + Text { + /// The full text of the license. + text: String, + }, + File { + /// The file containing the license text. + file: PathBuf, + }, +} + +/// A `project.authors` or `project.maintainers` entry as specified in +/// . +/// +/// The entry is derived from the email format of `John Doe `. You need to +/// provide at least name or email. +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged, expecting = "a table with 'name' and/or 'email' keys")] +enum Contact { + /// TODO(konsti): RFC 822 validation. + Name { name: String }, + /// TODO(konsti): RFC 822 validation. + Email { email: String }, + /// TODO(konsti): RFC 822 validation. + NameEmail { name: String, email: String }, +} + +/// The `[build-system]` section of a pyproject.toml as specified in PEP 517. +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +struct BuildSystem { + /// PEP 508 dependencies required to execute the build system. + requires: Vec>, + /// A string naming a Python object that will be used to perform the build. + build_backend: Option, + /// + backend_path: Option>, +} + +#[cfg(test)] +mod tests; diff --git a/crates/uv-build-backend/src/metadata/tests.rs b/crates/uv-build-backend/src/metadata/tests.rs new file mode 100644 index 000000000..e0aaa1c18 --- /dev/null +++ b/crates/uv-build-backend/src/metadata/tests.rs @@ -0,0 +1,401 @@ +use super::*; +use indoc::{formatdoc, indoc}; +use insta::assert_snapshot; +use std::iter; +use tempfile::TempDir; + +fn extend_project(payload: &str) -> String { + formatdoc! {r#" + [project] + name = "hello-world" + version = "0.1.0" + {payload} + + [build-system] + requires = ["uv>=0.4.15,<5"] + build-backend = "uv" + "# + } +} + +fn format_err(err: impl std::error::Error) -> String { + let mut formatted = err.to_string(); + for source in iter::successors(err.source(), |&err| err.source()) { + formatted += &format!("\n Caused by: {source}"); + } + formatted +} + +#[test] +fn valid() { + let temp_dir = TempDir::new().unwrap(); + + fs_err::write( + temp_dir.path().join("Readme.md"), + indoc! {r" + # Foo + + This is the foo library. + "}, + ) + .unwrap(); + + fs_err::write( + temp_dir.path().join("License.txt"), + indoc! {r#" + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "#}, + ) + .unwrap(); + + let contents = indoc! {r#" + # See https://github.com/pypa/sampleproject/blob/main/pyproject.toml for another example + + [project] + name = "hello-world" + version = "0.1.0" + description = "A Python package" + readme = "Readme.md" + requires_python = ">=3.12" + license = { file = "License.txt" } + authors = [{ name = "Ferris the crab", email = "ferris@rustacean.net" }] + maintainers = [{ name = "Konsti", email = "konstin@mailbox.org" }] + keywords = ["demo", "example", "package"] + classifiers = [ + "Development Status :: 6 - Mature", + "License :: OSI Approved :: MIT License", + # https://github.com/pypa/trove-classifiers/issues/17 + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + ] + dependencies = ["flask>=3,<4", "sqlalchemy[asyncio]>=2.0.35,<3"] + # We don't support dynamic fields, the default empty array is the only allowed value. + dynamic = [] + + [project.optional-dependencies] + postgres = ["psycopg>=3.2.2,<4"] + mysql = ["pymysql>=1.1.1,<2"] + + [project.urls] + "Homepage" = "https://github.com/astral-sh/uv" + "Repository" = "https://astral.sh" + + [project.scripts] + foo = "foo.cli:__main__" + + [project.gui-scripts] + foo-gui = "foo.gui" + + [project.entry-points.bar_group] + foo-bar = "foo:bar" + + [build-system] + requires = ["uv>=0.4.15,<5"] + build-backend = "uv" + "# + }; + + let pyproject_toml = PyProjectToml::parse(contents).unwrap(); + let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap(); + + assert_snapshot!(metadata.core_metadata_format(), @r###" + Metadata-Version: 2.3 + Name: hello-world + Version: 0.1.0 + Summary: A Python package + Keywords: demo,example,package + Author: Ferris the crab + License: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + Classifier: Development Status :: 6 - Mature + Classifier: License :: OSI Approved :: MIT License + Classifier: License :: OSI Approved :: Apache Software License + Classifier: Programming Language :: Python + Requires-Dist: flask>=3,<4 + Requires-Dist: sqlalchemy[asyncio]>=2.0.35,<3 + Maintainer: Konsti + Project-URL: Homepage, https://github.com/astral-sh/uv + Project-URL: Repository, https://astral.sh + Provides-Extra: mysql + Provides-Extra: postgres + Description-Content-Type: text/markdown + + # Foo + + This is the foo library. + "###); + + assert_snapshot!(pyproject_toml.to_entry_points().unwrap().unwrap(), @r###" + [console_scripts] + foo = foo.cli:__main__ + + [gui_scripts] + foo-gui = foo.gui + + [bar_group] + foo-bar = foo:bar + + "###); +} + +#[test] +fn build_system_valid() { + let contents = extend_project(""); + let pyproject_toml = PyProjectToml::parse(&contents).unwrap(); + assert!(pyproject_toml.check_build_system("1.0.0+test")); +} + +#[test] +fn build_system_no_bound() { + let contents = indoc! {r#" + [project] + name = "hello-world" + version = "0.1.0" + + [build-system] + requires = ["uv"] + build-backend = "uv" + "#}; + let pyproject_toml = PyProjectToml::parse(contents).unwrap(); + assert!(!pyproject_toml.check_build_system("1.0.0+test")); +} + +#[test] +fn build_system_multiple_packages() { + let contents = indoc! {r#" + [project] + name = "hello-world" + version = "0.1.0" + + [build-system] + requires = ["uv>=0.4.15,<5", "wheel"] + build-backend = "uv" + "#}; + let pyproject_toml = PyProjectToml::parse(contents).unwrap(); + assert!(!pyproject_toml.check_build_system("1.0.0+test")); +} + +#[test] +fn build_system_no_requires_uv() { + let contents = indoc! {r#" + [project] + name = "hello-world" + version = "0.1.0" + + [build-system] + requires = ["setuptools"] + build-backend = "uv" + "#}; + let pyproject_toml = PyProjectToml::parse(contents).unwrap(); + assert!(!pyproject_toml.check_build_system("1.0.0+test")); +} + +#[test] +fn build_system_not_uv() { + let contents = indoc! {r#" + [project] + name = "hello-world" + version = "0.1.0" + + [build-system] + requires = ["uv>=0.4.15,<5"] + build-backend = "setuptools" + "#}; + let pyproject_toml = PyProjectToml::parse(contents).unwrap(); + assert!(!pyproject_toml.check_build_system("1.0.0+test")); +} + +#[test] +fn minimal() { + let contents = extend_project(""); + + let metadata = PyProjectToml::parse(&contents) + .unwrap() + .to_metadata(Path::new("/do/not/read")) + .unwrap(); + + assert_snapshot!(metadata.core_metadata_format(), @r###" + Metadata-Version: 2.3 + Name: hello-world + Version: 0.1.0 + "###); +} + +#[test] +fn invalid_readme_spec() { + let contents = extend_project(indoc! {r#" + readme = { path = "Readme.md" } + "# + }); + + let err = PyProjectToml::parse(&contents).unwrap_err(); + assert_snapshot!(format_err(err), @r###" + Invalid pyproject.toml + Caused by: TOML parse error at line 4, column 10 + | + 4 | readme = { path = "Readme.md" } + | ^^^^^^^^^^^^^^^^^^^^^^ + data did not match any variant of untagged enum Readme + "###); +} + +#[test] +fn missing_readme() { + let contents = extend_project(indoc! {r#" + readme = "Readme.md" + "# + }); + + let err = PyProjectToml::parse(&contents) + .unwrap() + .to_metadata(Path::new("/do/not/read")) + .unwrap_err(); + // Simplified for windows compatibility. + assert_snapshot!(err.to_string().replace('\\', "/"), @"failed to open file `/do/not/read/Readme.md`"); +} + +#[test] +fn multiline_description() { + let contents = extend_project(indoc! {r#" + description = "Hi :)\nThis is my project" + "# + }); + + let err = PyProjectToml::parse(&contents) + .unwrap() + .to_metadata(Path::new("/do/not/read")) + .unwrap_err(); + assert_snapshot!(format_err(err), @r###" + Invalid pyproject.toml + Caused by: `project.description` must be a single line + "###); +} + +#[test] +fn mixed_licenses() { + let contents = extend_project(indoc! {r#" + license-files = ["licenses/*"] + license = { text = "MIT" } + "# + }); + + let err = PyProjectToml::parse(&contents) + .unwrap() + .to_metadata(Path::new("/do/not/read")) + .unwrap_err(); + assert_snapshot!(format_err(err), @r###" + Invalid pyproject.toml + Caused by: When `project.license-files` is defined, `project.license` must be an SPDX expression string + "###); +} + +#[test] +fn valid_license() { + let contents = extend_project(indoc! {r#" + license = "MIT OR Apache-2.0" + "# + }); + let metadata = PyProjectToml::parse(&contents) + .unwrap() + .to_metadata(Path::new("/do/not/read")) + .unwrap(); + assert_snapshot!(metadata.core_metadata_format(), @r###" + Metadata-Version: 2.4 + Name: hello-world + Version: 0.1.0 + License-Expression: MIT OR Apache-2.0 + "###); +} + +#[test] +fn invalid_license() { + let contents = extend_project(indoc! {r#" + license = "MIT XOR Apache-2" + "# + }); + let err = PyProjectToml::parse(&contents) + .unwrap() + .to_metadata(Path::new("/do/not/read")) + .unwrap_err(); + // TODO(konsti): We mess up the indentation in the error. + assert_snapshot!(format_err(err), @r###" + Invalid pyproject.toml + Caused by: `project.license` is not a valid SPDX expression: `MIT XOR Apache-2` + Caused by: MIT XOR Apache-2 + ^^^ unknown term + "###); +} + +#[test] +fn dynamic() { + let contents = extend_project(indoc! {r#" + dynamic = ["dependencies"] + "# + }); + + let err = PyProjectToml::parse(&contents) + .unwrap() + .to_metadata(Path::new("/do/not/read")) + .unwrap_err(); + assert_snapshot!(format_err(err), @r###" + Invalid pyproject.toml + Caused by: Dynamic metadata is not supported + "###); +} + +fn script_error(contents: &str) -> String { + let err = PyProjectToml::parse(contents) + .unwrap() + .to_entry_points() + .unwrap_err(); + format_err(err) +} + +#[test] +fn invalid_entry_point_group() { + let contents = extend_project(indoc! {r#" + [project.entry-points."a@b"] + foo = "bar" + "# + }); + assert_snapshot!(script_error(&contents), @"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `a@b`"); +} + +#[test] +fn invalid_entry_point_name() { + let contents = extend_project(indoc! {r#" + [project.scripts] + "a@b" = "bar" + "# + }); + assert_snapshot!(script_error(&contents), @"Entrypoint names must consist of letters, numbers, dots and dashes; invalid name: `a@b`"); +} + +#[test] +fn invalid_entry_point_conflict_scripts() { + let contents = extend_project(indoc! {r#" + [project.entry-points.console_scripts] + foo = "bar" + "# + }); + assert_snapshot!(script_error(&contents), @"Use `project.scripts` instead of `project.entry-points.console_scripts`"); +} + +#[test] +fn invalid_entry_point_conflict_gui_scripts() { + let contents = extend_project(indoc! {r#" + [project.entry-points.gui_scripts] + foo = "bar" + "# + }); + assert_snapshot!(script_error(&contents), @"Use `project.gui-scripts` instead of `project.entry-points.gui_scripts`"); +} diff --git a/crates/uv-build-backend/src/pep639_glob.rs b/crates/uv-build-backend/src/pep639_glob.rs new file mode 100644 index 000000000..aae4d68f2 --- /dev/null +++ b/crates/uv-build-backend/src/pep639_glob.rs @@ -0,0 +1,81 @@ +//! Implementation of PEP 639 cross-language restricted globs. + +use glob::{Pattern, PatternError}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Pep639GlobError { + #[error(transparent)] + PatternError(#[from] PatternError), + #[error("The parent directory operator (`..`) at position {pos} is not allowed in license file globs")] + ParentDirectory { pos: usize }, + #[error("Glob contains invalid character at position {pos}: `{invalid}`")] + InvalidCharacter { pos: usize, invalid: char }, + #[error("Glob contains invalid character in range at position {pos}: `{invalid}`")] + InvalidCharacterRange { pos: usize, invalid: char }, +} + +/// Parse a PEP 639 `license-files` glob. +/// +/// The syntax is more restricted than regular globbing in Python or Rust for platform independent +/// results. Since [`glob::Pattern`] is a superset over this format, we can use it after validating +/// that no unsupported features are in the string. +/// +/// From [PEP 639](https://peps.python.org/pep-0639/#add-license-files-key): +/// +/// > Its value is an array of strings which MUST contain valid glob patterns, +/// > as specified below: +/// > +/// > - Alphanumeric characters, underscores (`_`), hyphens (`-`) and dots (`.`) +/// > MUST be matched verbatim. +/// > +/// > - Special glob characters: `*`, `?`, `**` and character ranges: `[]` +/// > containing only the verbatim matched characters MUST be supported. +/// > Within `[...]`, the hyphen indicates a range (e.g. `a-z`). +/// > Hyphens at the start or end are matched literally. +/// > +/// > - Path delimiters MUST be the forward slash character (`/`). +/// > Patterns are relative to the directory containing `pyproject.toml`, +/// > therefore the leading slash character MUST NOT be used. +/// > +/// > - Parent directory indicators (`..`) MUST NOT be used. +/// > +/// > Any characters or character sequences not covered by this specification are +/// > invalid. Projects MUST NOT use such values. +/// > Tools consuming this field MAY reject invalid values with an error. +pub(crate) fn parse_pep639_glob(glob: &str) -> Result { + let mut chars = glob.chars().enumerate().peekable(); + // A `..` is on a parent directory indicator at the start of the string or after a directory + // separator. + let mut start_or_slash = true; + while let Some((pos, c)) = chars.next() { + if c.is_alphanumeric() || matches!(c, '_' | '-' | '*' | '?') { + start_or_slash = false; + } else if c == '.' { + if start_or_slash && matches!(chars.peek(), Some((_, '.'))) { + return Err(Pep639GlobError::ParentDirectory { pos }); + } + start_or_slash = false; + } else if c == '/' { + start_or_slash = true; + } else if c == '[' { + for (pos, c) in chars.by_ref() { + // TODO: https://discuss.python.org/t/pep-639-round-3-improving-license-clarity-with-better-package-metadata/53020/98 + if c.is_alphanumeric() || matches!(c, '_' | '-' | '.') { + // Allowed. + } else if c == ']' { + break; + } else { + return Err(Pep639GlobError::InvalidCharacterRange { pos, invalid: c }); + } + } + start_or_slash = false; + } else { + return Err(Pep639GlobError::InvalidCharacter { pos, invalid: c }); + } + } + Ok(Pattern::new(glob)?) +} + +#[cfg(test)] +mod tests; diff --git a/crates/uv-build-backend/src/pep639_glob/tests.rs b/crates/uv-build-backend/src/pep639_glob/tests.rs new file mode 100644 index 000000000..1bb02520c --- /dev/null +++ b/crates/uv-build-backend/src/pep639_glob/tests.rs @@ -0,0 +1,54 @@ +use super::*; +use insta::assert_snapshot; + +#[test] +fn test_error() { + let parse_err = |glob| parse_pep639_glob(glob).unwrap_err().to_string(); + assert_snapshot!( + parse_err(".."), + @"The parent directory operator (`..`) at position 0 is not allowed in license file globs" + ); + assert_snapshot!( + parse_err("licenses/.."), + @"The parent directory operator (`..`) at position 9 is not allowed in license file globs" + ); + assert_snapshot!( + parse_err("licenses/LICEN!E.txt"), + @"Glob contains invalid character at position 14: `!`" + ); + assert_snapshot!( + parse_err("licenses/LICEN[!C]E.txt"), + @"Glob contains invalid character in range at position 15: `!`" + ); + assert_snapshot!( + parse_err("licenses/LICEN[C?]E.txt"), + @"Glob contains invalid character in range at position 16: `?`" + ); + assert_snapshot!(parse_err("******"), @"Pattern syntax error near position 2: wildcards are either regular `*` or recursive `**`"); + assert_snapshot!( + parse_err(r"licenses\eula.txt"), + @r"Glob contains invalid character at position 8: `\`" + ); +} + +#[test] +fn test_valid() { + let cases = [ + "licenses/*.txt", + "licenses/**/*.txt", + "LICEN[CS]E.txt", + "LICEN?E.txt", + "[a-z].txt", + "[a-z._-].txt", + "*/**", + "LICENSE..txt", + "LICENSE_file-1.txt", + // (google translate) + "licenses/라이센스*.txt", + "licenses/ライセンス*.txt", + "licenses/执照*.txt", + ]; + for case in cases { + parse_pep639_glob(case).unwrap(); + } +} diff --git a/crates/uv-build-backend/src/tests.rs b/crates/uv-build-backend/src/tests.rs new file mode 100644 index 000000000..f90d7d632 --- /dev/null +++ b/crates/uv-build-backend/src/tests.rs @@ -0,0 +1,128 @@ +use super::*; +use insta::assert_snapshot; +use std::str::FromStr; +use tempfile::TempDir; +use uv_normalize::PackageName; +use uv_pep440::Version; + +#[test] +fn test_wheel() { + let filename = WheelFilename { + name: PackageName::from_str("foo").unwrap(), + version: Version::from_str("1.2.3").unwrap(), + build_tag: None, + python_tag: vec!["py2".to_string(), "py3".to_string()], + abi_tag: vec!["none".to_string()], + platform_tag: vec!["any".to_string()], + }; + + assert_snapshot!(wheel_info(&filename, "1.0.0+test"), @r" + Wheel-Version: 1.0 + Generator: uv 1.0.0+test + Root-Is-Purelib: true + Tag: py2-none-any + Tag: py3-none-any + "); +} + +#[test] +fn test_record() { + let record = vec![RecordEntry { + path: "uv_backend/__init__.py".to_string(), + hash: "89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865".to_string(), + size: 37, + }]; + + let mut writer = Vec::new(); + write_record(&mut writer, "uv_backend-0.1.0", record).unwrap(); + assert_snapshot!(String::from_utf8(writer).unwrap(), @r" + uv_backend/__init__.py,sha256=89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865,37 + uv_backend-0.1.0/RECORD,, + "); +} + +/// Check that we write deterministic wheels. +#[test] +fn test_determinism() { + let temp1 = TempDir::new().unwrap(); + let uv_backend = Path::new("../../scripts/packages/uv_backend"); + build(uv_backend, temp1.path(), None, "1.0.0+test").unwrap(); + + // Touch the file to check that we don't serialize the last modified date. + fs_err::write( + uv_backend.join("src/uv_backend/__init__.py"), + "def greet():\n print(\"Hello 👋\")\n", + ) + .unwrap(); + + let temp2 = TempDir::new().unwrap(); + build(uv_backend, temp2.path(), None, "1.0.0+test").unwrap(); + + let wheel_filename = "uv_backend-0.1.0-py3-none-any.whl"; + assert_eq!( + fs_err::read(temp1.path().join(wheel_filename)).unwrap(), + fs_err::read(temp2.path().join(wheel_filename)).unwrap() + ); +} + +/// Snapshot all files from the prepare metadata hook. +#[test] +fn test_prepare_metadata() { + let metadata_dir = TempDir::new().unwrap(); + let uv_backend = Path::new("../../scripts/packages/uv_backend"); + metadata(uv_backend, metadata_dir.path(), "1.0.0+test").unwrap(); + + let mut files: Vec<_> = WalkDir::new(metadata_dir.path()) + .into_iter() + .map(|entry| { + entry + .unwrap() + .path() + .strip_prefix(metadata_dir.path()) + .unwrap() + .portable_display() + .to_string() + }) + .filter(|path| !path.is_empty()) + .collect(); + files.sort(); + assert_snapshot!(files.join("\n"), @r" + uv_backend-0.1.0.dist-info + uv_backend-0.1.0.dist-info/METADATA + uv_backend-0.1.0.dist-info/RECORD + uv_backend-0.1.0.dist-info/WHEEL + "); + + let metadata_file = metadata_dir + .path() + .join("uv_backend-0.1.0.dist-info/METADATA"); + assert_snapshot!(fs_err::read_to_string(metadata_file).unwrap(), @r###" + Metadata-Version: 2.3 + Name: uv-backend + Version: 0.1.0 + Summary: Add your description here + Requires-Python: >=3.12 + Description-Content-Type: text/markdown + + # uv_backend + + A simple package to be built with the uv build backend. + "###); + + let record_file = metadata_dir + .path() + .join("uv_backend-0.1.0.dist-info/RECORD"); + assert_snapshot!(fs_err::read_to_string(record_file).unwrap(), @r###" + uv_backend-0.1.0.dist-info/WHEEL,sha256=3da1bfa0e8fd1b6cc246aa0b2b44a35815596c600cb485c39a6f8c106c3d5a8d,83 + uv_backend-0.1.0.dist-info/METADATA,sha256=e4a0d390317d7182f65ea978254c71ed283e0a4242150cf1c99a694b113ff68d,224 + uv_backend-0.1.0.dist-info/RECORD,, + "###); + + let wheel_file = metadata_dir.path().join("uv_backend-0.1.0.dist-info/WHEEL"); + assert_snapshot!(fs_err::read_to_string(wheel_file).unwrap(), @r###" + Wheel-Version: 1.0 + Generator: uv 1.0.0+test + Root-Is-Purelib: true + Tag: py3-none-any + "###); +} diff --git a/crates/uv-build-frontend/Cargo.toml b/crates/uv-build-frontend/Cargo.toml index 8ad038c62..a0e8fd89a 100644 --- a/crates/uv-build-frontend/Cargo.toml +++ b/crates/uv-build-frontend/Cargo.toml @@ -10,17 +10,22 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true [dependencies] uv-configuration = { workspace = true } +uv-distribution = { workspace = true } uv-distribution-types = { workspace = true } uv-fs = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } uv-python = { workspace = true } +uv-static = { workspace = true } uv-types = { workspace = true } uv-virtualenv = { workspace = true } diff --git a/crates/uv-build-frontend/src/error.rs b/crates/uv-build-frontend/src/error.rs index 969018dcd..db66c1b5b 100644 --- a/crates/uv-build-frontend/src/error.rs +++ b/crates/uv-build-frontend/src/error.rs @@ -57,6 +57,8 @@ static DISTUTILS_NOT_FOUND_RE: LazyLock = pub enum Error { #[error(transparent)] Io(#[from] io::Error), + #[error(transparent)] + Lowering(#[from] uv_distribution::MetadataError), #[error("{} does not appear to be a Python project, as neither `pyproject.toml` nor `setup.py` are present in the directory", _0.simplified_display())] InvalidSourceDist(PathBuf), #[error("Invalid `pyproject.toml`")] diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index b59edbb2d..ca7eded39 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -27,16 +27,19 @@ use tokio::process::Command; use tokio::sync::{Mutex, Semaphore}; use tracing::{debug, info_span, instrument, Instrument}; -pub use crate::error::{Error, MissingHeaderCause}; -use uv_configuration::{BuildKind, BuildOutput, ConfigSettings}; -use uv_distribution_types::Resolution; +use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, LowerBound, SourceStrategy}; +use uv_distribution::RequiresDist; +use uv_distribution_types::{IndexLocations, Resolution}; use uv_fs::{rename_with_retry, PythonExt, Simplified}; use uv_pep440::Version; use uv_pep508::PackageName; use uv_pypi_types::{Requirement, VerbatimParsedUrl}; use uv_python::{Interpreter, PythonEnvironment}; +use uv_static::EnvVars; use uv_types::{BuildContext, BuildIsolation, SourceBuildTrait}; +pub use crate::error::{Error, MissingHeaderCause}; + /// The default backend to use when PEP 517 is used without a `build-system` section. static DEFAULT_BACKEND: LazyLock = LazyLock::new(|| Pep517Backend { backend: "setuptools.build_meta:__legacy__".to_string(), @@ -242,12 +245,15 @@ impl SourceBuild { pub async fn setup( source: &Path, subdirectory: Option<&Path>, + install_path: &Path, fallback_package_name: Option<&PackageName>, fallback_package_version: Option<&Version>, interpreter: &Interpreter, build_context: &impl BuildContext, source_build_context: SourceBuildContext, version_id: Option, + locations: &IndexLocations, + source_strategy: SourceStrategy, config_settings: ConfigSettings, build_isolation: BuildIsolation<'_>, build_kind: BuildKind, @@ -266,8 +272,16 @@ impl SourceBuild { let default_backend: Pep517Backend = DEFAULT_BACKEND.clone(); // Check if we have a PEP 517 build backend. - let (pep517_backend, project) = - Self::extract_pep517_backend(&source_tree, &default_backend).map_err(|err| *err)?; + let (pep517_backend, project) = Self::extract_pep517_backend( + &source_tree, + install_path, + fallback_package_name, + locations, + source_strategy, + &default_backend, + ) + .await + .map_err(|err| *err)?; let package_name = project .as_ref() @@ -318,10 +332,10 @@ impl SourceBuild { // Figure out what the modified path should be, and remove the PATH variable from the // environment variables if it's there. - let user_path = environment_variables.remove(&OsString::from("PATH")); + let user_path = environment_variables.remove(&OsString::from(EnvVars::PATH)); // See if there is an OS PATH variable. - let os_path = env::var_os("PATH"); + let os_path = env::var_os(EnvVars::PATH); // Prepend the user supplied PATH to the existing OS PATH let modified_path = if let Some(user_path) = user_path { @@ -356,12 +370,15 @@ impl SourceBuild { create_pep517_build_environment( &runner, &source_tree, + install_path, &venv, &pep517_backend, build_context, package_name.as_ref(), package_version.as_ref(), version_id.as_deref(), + locations, + source_strategy, build_kind, level, &config_settings, @@ -420,8 +437,12 @@ impl SourceBuild { } /// Extract the PEP 517 backend from the `pyproject.toml` or `setup.py` file. - fn extract_pep517_backend( + async fn extract_pep517_backend( source_tree: &Path, + install_path: &Path, + package_name: Option<&PackageName>, + locations: &IndexLocations, + source_strategy: SourceStrategy, default_backend: &Pep517Backend, ) -> Result<(Pep517Backend, Option), Box> { match fs::read_to_string(source_tree.join("pyproject.toml")) { @@ -432,7 +453,49 @@ impl SourceBuild { let pyproject_toml: PyProjectToml = PyProjectToml::deserialize(pyproject_toml.into_deserializer()) .map_err(Error::InvalidPyprojectTomlSchema)?; + let backend = if let Some(build_system) = pyproject_toml.build_system { + // If necessary, lower the requirements. + let requirements = match source_strategy { + SourceStrategy::Enabled => { + if let Some(name) = pyproject_toml + .project + .as_ref() + .map(|project| &project.name) + .or(package_name) + { + // TODO(charlie): Add a type to lower requirements without providing + // empty extras. + let requires_dist = uv_pypi_types::RequiresDist { + name: name.clone(), + requires_dist: build_system.requires, + provides_extras: vec![], + }; + let requires_dist = RequiresDist::from_project_maybe_workspace( + requires_dist, + install_path, + locations, + source_strategy, + LowerBound::Allow, + ) + .await + .map_err(Error::Lowering)?; + requires_dist.requires_dist + } else { + build_system + .requires + .into_iter() + .map(Requirement::from) + .collect() + } + } + SourceStrategy::Disabled => build_system + .requires + .into_iter() + .map(Requirement::from) + .collect(), + }; + Pep517Backend { // If `build-backend` is missing, inject the legacy setuptools backend, but // retain the `requires`, to match `pip` and `build`. Note that while PEP 517 @@ -445,11 +508,7 @@ impl SourceBuild { .build_backend .unwrap_or_else(|| "setuptools.build_meta:__legacy__".to_string()), backend_path: build_system.backend_path, - requirements: build_system - .requires - .into_iter() - .map(Requirement::from) - .collect(), + requirements, } } else { // If a `pyproject.toml` is present, but `[build-system]` is missing, proceed with @@ -748,12 +807,15 @@ fn escape_path_for_python(path: &Path) -> String { async fn create_pep517_build_environment( runner: &PythonRunner, source_tree: &Path, + install_path: &Path, venv: &PythonEnvironment, pep517_backend: &Pep517Backend, build_context: &impl BuildContext, package_name: Option<&PackageName>, package_version: Option<&Version>, version_id: Option<&str>, + locations: &IndexLocations, + source_strategy: SourceStrategy, build_kind: BuildKind, level: BuildOutput, config_settings: &ConfigSettings, @@ -850,7 +912,34 @@ async fn create_pep517_build_environment( version_id, ) })?; - let extra_requires: Vec<_> = extra_requires.into_iter().map(Requirement::from).collect(); + + // If necessary, lower the requirements. + let extra_requires = match source_strategy { + SourceStrategy::Enabled => { + if let Some(package_name) = package_name { + // TODO(charlie): Add a type to lower requirements without providing + // empty extras. + let requires_dist = uv_pypi_types::RequiresDist { + name: package_name.clone(), + requires_dist: extra_requires, + provides_extras: vec![], + }; + let requires_dist = RequiresDist::from_project_maybe_workspace( + requires_dist, + install_path, + locations, + source_strategy, + LowerBound::Allow, + ) + .await + .map_err(Error::Lowering)?; + requires_dist.requires_dist + } else { + extra_requires.into_iter().map(Requirement::from).collect() + } + } + SourceStrategy::Disabled => extra_requires.into_iter().map(Requirement::from).collect(), + }; // Some packages (such as tqdm 4.66.1) list only extra requires that have already been part of // the pyproject.toml requires (in this case, `wheel`). We can skip doing the whole resolution @@ -921,13 +1010,15 @@ impl PythonRunner { ) -> Result { /// Read lines from a reader and store them in a buffer. async fn read_from( - mut reader: tokio::io::Lines>, + mut reader: tokio::io::Split>, mut printer: Printer, buffer: &mut Vec, ) -> io::Result<()> { loop { - match reader.next_line().await? { - Some(line) => { + match reader.next_segment().await? { + Some(line_buf) => { + let line_buf = line_buf.strip_suffix(b"\r").unwrap_or(&line_buf); + let line = String::from_utf8_lossy(line_buf).into(); let _ = write!(printer, "{line}"); buffer.push(line); } @@ -942,10 +1033,10 @@ impl PythonRunner { .args(["-c", script]) .current_dir(source_tree.simplified()) .envs(environment_variables) - .env("PATH", modified_path) - .env("VIRTUAL_ENV", venv.root()) - .env("CLICOLOR_FORCE", "1") - .env("PYTHONIOENCODING", "utf-8") + .env(EnvVars::PATH, modified_path) + .env(EnvVars::VIRTUAL_ENV, venv.root()) + .env(EnvVars::CLICOLOR_FORCE, "1") + .env(EnvVars::PYTHONIOENCODING, "utf-8:backslashreplace") .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .spawn() @@ -956,8 +1047,8 @@ impl PythonRunner { let mut stderr_buf = Vec::with_capacity(1024); // Create separate readers for `stdout` and `stderr`. - let stdout_reader = tokio::io::BufReader::new(child.stdout.take().unwrap()).lines(); - let stderr_reader = tokio::io::BufReader::new(child.stderr.take().unwrap()).lines(); + let stdout_reader = tokio::io::BufReader::new(child.stdout.take().unwrap()).split(b'\n'); + let stderr_reader = tokio::io::BufReader::new(child.stderr.take().unwrap()).split(b'\n'); // Asynchronously read from the in-memory pipes. let printer = Printer::from(self.level); diff --git a/crates/uv-cache-info/Cargo.toml b/crates/uv-cache-info/Cargo.toml index d1c594a66..ec17a4416 100644 --- a/crates/uv-cache-info/Cargo.toml +++ b/crates/uv-cache-info/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-cache-info/src/cache_info.rs b/crates/uv-cache-info/src/cache_info.rs index a6ee83959..7a62ded3d 100644 --- a/crates/uv-cache-info/src/cache_info.rs +++ b/crates/uv-cache-info/src/cache_info.rs @@ -1,4 +1,4 @@ -use crate::commit_info::CacheCommit; +use crate::git_info::{Commit, Tags}; use crate::timestamp::Timestamp; use serde::Deserialize; @@ -26,7 +26,9 @@ pub struct CacheInfo { /// files to timestamp via the `cache-keys` field. timestamp: Option, /// The commit at which the distribution was built. - commit: Option, + commit: Option, + /// The Git tags present at the time of the build. + tags: Option, } impl CacheInfo { @@ -51,6 +53,7 @@ impl CacheInfo { /// Compute the cache info for a given directory. pub fn from_directory(directory: &Path) -> Result { let mut commit = None; + let mut tags = None; let mut timestamp = None; // Read the cache keys. @@ -92,6 +95,9 @@ impl CacheInfo { let path = directory.join(file); let metadata = match path.metadata() { Ok(metadata) => metadata, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + continue; + } Err(err) => { warn!("Failed to read metadata for file: {err}"); continue; @@ -106,13 +112,37 @@ impl CacheInfo { } timestamp = max(timestamp, Some(Timestamp::from_metadata(&metadata))); } - CacheKey::Git { git: true } => match CacheCommit::from_repository(directory) { + CacheKey::Git { + git: GitPattern::Bool(true), + } => match Commit::from_repository(directory) { Ok(commit_info) => commit = Some(commit_info), Err(err) => { debug!("Failed to read the current commit: {err}"); } }, - CacheKey::Git { git: false } => {} + CacheKey::Git { + git: GitPattern::Set(set), + } => { + if set.commit.unwrap_or(false) { + match Commit::from_repository(directory) { + Ok(commit_info) => commit = Some(commit_info), + Err(err) => { + debug!("Failed to read the current commit: {err}"); + } + } + } + if set.tags.unwrap_or(false) { + match Tags::from_repository(directory) { + Ok(tags_info) => tags = Some(tags_info), + Err(err) => { + debug!("Failed to read the current tags: {err}"); + } + } + } + } + CacheKey::Git { + git: GitPattern::Bool(false), + } => {} } } @@ -147,7 +177,11 @@ impl CacheInfo { } } - Ok(Self { timestamp, commit }) + Ok(Self { + timestamp, + commit, + tags, + }) } /// Compute the cache info for a given file, assumed to be a binary or source distribution @@ -162,14 +196,18 @@ impl CacheInfo { } pub fn is_empty(&self) -> bool { - self.timestamp.is_none() && self.commit.is_none() + self.timestamp.is_none() && self.commit.is_none() && self.tags.is_none() } } #[derive(Debug, serde::Deserialize)] struct TimestampCommit { + #[serde(default)] timestamp: Option, - commit: Option, + #[serde(default)] + commit: Option, + #[serde(default)] + tags: Option, } #[derive(Debug, serde::Deserialize)] @@ -189,9 +227,15 @@ impl From for CacheInfo { timestamp: Some(timestamp), ..Self::default() }, - CacheInfoWire::TimestampCommit(TimestampCommit { timestamp, commit }) => { - Self { timestamp, commit } - } + CacheInfoWire::TimestampCommit(TimestampCommit { + timestamp, + commit, + tags, + }) => Self { + timestamp, + commit, + tags, + }, } } } @@ -223,8 +267,24 @@ pub enum CacheKey { Path(String), /// Ex) `{ file = "Cargo.lock" }` or `{ file = "**/*.toml" }` File { file: String }, - /// Ex) `{ git = true }` - Git { git: bool }, + /// Ex) `{ git = true }` or `{ git = { commit = true, tags = false } }` + Git { git: GitPattern }, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)] +pub enum GitPattern { + Bool(bool), + Set(GitSet), +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct GitSet { + commit: Option, + tags: Option, } pub enum FilePattern { diff --git a/crates/uv-cache-info/src/commit_info.rs b/crates/uv-cache-info/src/commit_info.rs deleted file mode 100644 index c3041be2c..000000000 --- a/crates/uv-cache-info/src/commit_info.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::path::{Path, PathBuf}; - -#[derive(Debug, thiserror::Error)] -pub(crate) enum CacheCommitError { - #[error("The repository at {0} is missing a `.git` directory")] - MissingGitDir(PathBuf), - #[error("The repository at {0} is missing a `HEAD` file")] - MissingHead(PathBuf), - #[error("The repository at {0} has an invalid reference: `{1}`")] - InvalidRef(PathBuf, String), - #[error("The discovered commit has an invalid length (expected 40 characters): `{0}`")] - WrongLength(String), - #[error("The discovered commit has an invalid character (expected hexadecimal): `{0}`")] - WrongDigit(String), - #[error(transparent)] - Io(#[from] std::io::Error), -} - -/// The current commit for a repository (i.e., a 40-character hexadecimal string). -#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, serde::Deserialize, serde::Serialize)] -pub(crate) struct CacheCommit(String); - -impl CacheCommit { - /// Return the [`CacheCommit`] for the repository at the given path. - pub(crate) fn from_repository(path: &Path) -> Result { - // Find the `.git` directory, searching through parent directories if necessary. - let git_dir = path - .ancestors() - .map(|ancestor| ancestor.join(".git")) - .find(|git_dir| git_dir.exists()) - .ok_or_else(|| CacheCommitError::MissingGitDir(path.to_path_buf()))?; - - let git_head_path = - git_head(&git_dir).ok_or_else(|| CacheCommitError::MissingHead(git_dir.clone()))?; - let git_head_contents = fs_err::read_to_string(git_head_path)?; - - // The contents are either a commit or a reference in the following formats - // - "" when the head is detached - // - "ref " when working on a branch - // If a commit, checking if the HEAD file has changed is sufficient - // If a ref, we need to add the head file for that ref to rebuild on commit - let mut git_ref_parts = git_head_contents.split_whitespace(); - let commit_or_ref = git_ref_parts.next().ok_or_else(|| { - CacheCommitError::InvalidRef(git_dir.clone(), git_head_contents.clone()) - })?; - let commit = if let Some(git_ref) = git_ref_parts.next() { - let git_ref_path = git_dir.join(git_ref); - let commit = fs_err::read_to_string(git_ref_path)?; - commit.trim().to_string() - } else { - commit_or_ref.to_string() - }; - - // The commit should be 40 hexadecimal characters. - if commit.len() != 40 { - return Err(CacheCommitError::WrongLength(commit)); - } - if commit.chars().any(|c| !c.is_ascii_hexdigit()) { - return Err(CacheCommitError::WrongDigit(commit)); - } - - Ok(Self(commit)) - } -} - -/// Return the path to the `HEAD` file of a Git repository, taking worktrees into account. -fn git_head(git_dir: &Path) -> Option { - // The typical case is a standard git repository. - let git_head_path = git_dir.join("HEAD"); - if git_head_path.exists() { - return Some(git_head_path); - } - if !git_dir.is_file() { - return None; - } - // If `.git/HEAD` doesn't exist and `.git` is actually a file, - // then let's try to attempt to read it as a worktree. If it's - // a worktree, then its contents will look like this, e.g.: - // - // gitdir: /home/andrew/astral/uv/main/.git/worktrees/pr2 - // - // And the HEAD file we want to watch will be at: - // - // /home/andrew/astral/uv/main/.git/worktrees/pr2/HEAD - let contents = fs_err::read_to_string(git_dir).ok()?; - let (label, worktree_path) = contents.split_once(':')?; - if label != "gitdir" { - return None; - } - let worktree_path = worktree_path.trim(); - Some(PathBuf::from(worktree_path)) -} diff --git a/crates/uv-cache-info/src/git_info.rs b/crates/uv-cache-info/src/git_info.rs new file mode 100644 index 000000000..9df098e69 --- /dev/null +++ b/crates/uv-cache-info/src/git_info.rs @@ -0,0 +1,173 @@ +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +#[derive(Debug, thiserror::Error)] +pub(crate) enum GitInfoError { + #[error("The repository at {0} is missing a `.git` directory")] + MissingGitDir(PathBuf), + #[error("The repository at {0} is missing a `HEAD` file")] + MissingHead(PathBuf), + #[error("The repository at {0} is missing a `refs` directory")] + MissingRefs(PathBuf), + #[error("The repository at {0} has an invalid reference: `{1}`")] + InvalidRef(PathBuf, String), + #[error("The discovered commit has an invalid length (expected 40 characters): `{0}`")] + WrongLength(String), + #[error("The discovered commit has an invalid character (expected hexadecimal): `{0}`")] + WrongDigit(String), + #[error(transparent)] + Io(#[from] std::io::Error), +} + +/// The current commit for a repository (i.e., a 40-character hexadecimal string). +#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +pub(crate) struct Commit(String); + +impl Commit { + /// Return the [`Commit`] for the repository at the given path. + pub(crate) fn from_repository(path: &Path) -> Result { + // Find the `.git` directory, searching through parent directories if necessary. + let git_dir = path + .ancestors() + .map(|ancestor| ancestor.join(".git")) + .find(|git_dir| git_dir.exists()) + .ok_or_else(|| GitInfoError::MissingGitDir(path.to_path_buf()))?; + + let git_head_path = + git_head(&git_dir).ok_or_else(|| GitInfoError::MissingHead(git_dir.clone()))?; + let git_head_contents = fs_err::read_to_string(git_head_path)?; + + // The contents are either a commit or a reference in the following formats + // - "" when the head is detached + // - "ref " when working on a branch + // If a commit, checking if the HEAD file has changed is sufficient + // If a ref, we need to add the head file for that ref to rebuild on commit + let mut git_ref_parts = git_head_contents.split_whitespace(); + let commit_or_ref = git_ref_parts + .next() + .ok_or_else(|| GitInfoError::InvalidRef(git_dir.clone(), git_head_contents.clone()))?; + let commit = if let Some(git_ref) = git_ref_parts.next() { + let git_ref_path = git_dir.join(git_ref); + let commit = fs_err::read_to_string(git_ref_path)?; + commit.trim().to_string() + } else { + commit_or_ref.to_string() + }; + + // The commit should be 40 hexadecimal characters. + if commit.len() != 40 { + return Err(GitInfoError::WrongLength(commit)); + } + if commit.chars().any(|c| !c.is_ascii_hexdigit()) { + return Err(GitInfoError::WrongDigit(commit)); + } + + Ok(Self(commit)) + } +} + +/// The set of tags visible in a repository. +#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +pub(crate) struct Tags(BTreeMap); + +impl Tags { + /// Return the [`Tags`] for the repository at the given path. + pub(crate) fn from_repository(path: &Path) -> Result { + // Find the `.git` directory, searching through parent directories if necessary. + let git_dir = path + .ancestors() + .map(|ancestor| ancestor.join(".git")) + .find(|git_dir| git_dir.exists()) + .ok_or_else(|| GitInfoError::MissingGitDir(path.to_path_buf()))?; + + let git_refs_path = + git_refs(&git_dir).ok_or_else(|| GitInfoError::MissingRefs(git_dir.clone()))?; + + let mut tags = BTreeMap::new(); + + // Map each tag to its commit. + let read_dir = match fs_err::read_dir(git_refs_path.join("tags")) { + Ok(read_dir) => read_dir, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + return Ok(Self(tags)); + } + Err(err) => return Err(err.into()), + }; + for entry in read_dir { + let entry = entry?; + let path = entry.path(); + if let Some(tag) = path.file_name().and_then(|name| name.to_str()) { + let commit = fs_err::read_to_string(&path)?.trim().to_string(); + + // The commit should be 40 hexadecimal characters. + if commit.len() != 40 { + return Err(GitInfoError::WrongLength(commit)); + } + if commit.chars().any(|c| !c.is_ascii_hexdigit()) { + return Err(GitInfoError::WrongDigit(commit)); + } + + tags.insert(tag.to_string(), commit); + } + } + + Ok(Self(tags)) + } +} + +/// Return the path to the `HEAD` file of a Git repository, taking worktrees into account. +fn git_head(git_dir: &Path) -> Option { + // The typical case is a standard git repository. + let git_head_path = git_dir.join("HEAD"); + if git_head_path.exists() { + return Some(git_head_path); + } + if !git_dir.is_file() { + return None; + } + // If `.git/HEAD` doesn't exist and `.git` is actually a file, + // then let's try to attempt to read it as a worktree. If it's + // a worktree, then its contents will look like this, e.g.: + // + // gitdir: /home/andrew/astral/uv/main/.git/worktrees/pr2 + // + // And the HEAD file we want to watch will be at: + // + // /home/andrew/astral/uv/main/.git/worktrees/pr2/HEAD + let contents = fs_err::read_to_string(git_dir).ok()?; + let (label, worktree_path) = contents.split_once(':')?; + if label != "gitdir" { + return None; + } + let worktree_path = worktree_path.trim(); + Some(PathBuf::from(worktree_path)) +} + +/// Return the path to the `refs` directory of a Git repository, taking worktrees into account. +fn git_refs(git_dir: &Path) -> Option { + // The typical case is a standard git repository. + let git_head_path = git_dir.join("refs"); + if git_head_path.exists() { + return Some(git_head_path); + } + if !git_dir.is_file() { + return None; + } + // If `.git/refs` doesn't exist and `.git` is actually a file, + // then let's try to attempt to read it as a worktree. If it's + // a worktree, then its contents will look like this, e.g.: + // + // gitdir: /home/andrew/astral/uv/main/.git/worktrees/pr2 + // + // And the HEAD refs we want to watch will be at: + // + // /home/andrew/astral/uv/main/.git/refs + let contents = fs_err::read_to_string(git_dir).ok()?; + let (label, worktree_path) = contents.split_once(':')?; + if label != "gitdir" { + return None; + } + let worktree_path = PathBuf::from(worktree_path.trim()); + let refs_path = worktree_path.parent()?.parent()?.join("refs"); + Some(refs_path) +} diff --git a/crates/uv-cache-info/src/lib.rs b/crates/uv-cache-info/src/lib.rs index c09398c7c..286411f68 100644 --- a/crates/uv-cache-info/src/lib.rs +++ b/crates/uv-cache-info/src/lib.rs @@ -2,5 +2,5 @@ pub use crate::cache_info::*; pub use crate::timestamp::*; mod cache_info; -mod commit_info; +mod git_info; mod timestamp; diff --git a/crates/uv-cache-key/Cargo.toml b/crates/uv-cache-key/Cargo.toml index 098a8f24d..7816c1d13 100644 --- a/crates/uv-cache-key/Cargo.toml +++ b/crates/uv-cache-key/Cargo.toml @@ -10,6 +10,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-cache-key/src/canonical_url.rs b/crates/uv-cache-key/src/canonical_url.rs index 0a866f9fa..9cc26d94c 100644 --- a/crates/uv-cache-key/src/canonical_url.rs +++ b/crates/uv-cache-key/src/canonical_url.rs @@ -181,144 +181,4 @@ impl std::fmt::Display for RepositoryUrl { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn user_credential_does_not_affect_cache_key() -> Result<(), url::ParseError> { - let mut hasher = CacheKeyHasher::new(); - CanonicalUrl::parse("https://example.com/pypa/sample-namespace-packages.git@2.0.0")? - .cache_key(&mut hasher); - let hash_without_creds = hasher.finish(); - - let mut hasher = CacheKeyHasher::new(); - CanonicalUrl::parse( - "https://user:foo@example.com/pypa/sample-namespace-packages.git@2.0.0", - )? - .cache_key(&mut hasher); - let hash_with_creds = hasher.finish(); - assert_eq!( - hash_without_creds, hash_with_creds, - "URLs with no user credentials should hash the same as URLs with different user credentials", - ); - - let mut hasher = CacheKeyHasher::new(); - CanonicalUrl::parse( - "https://user:bar@example.com/pypa/sample-namespace-packages.git@2.0.0", - )? - .cache_key(&mut hasher); - let hash_with_creds = hasher.finish(); - assert_eq!( - hash_without_creds, hash_with_creds, - "URLs with different user credentials should hash the same", - ); - - let mut hasher = CacheKeyHasher::new(); - CanonicalUrl::parse("https://:bar@example.com/pypa/sample-namespace-packages.git@2.0.0")? - .cache_key(&mut hasher); - let hash_with_creds = hasher.finish(); - assert_eq!( - hash_without_creds, hash_with_creds, - "URLs with no username, though with a password, should hash the same as URLs with different user credentials", - ); - - let mut hasher = CacheKeyHasher::new(); - CanonicalUrl::parse("https://user:@example.com/pypa/sample-namespace-packages.git@2.0.0")? - .cache_key(&mut hasher); - let hash_with_creds = hasher.finish(); - assert_eq!( - hash_without_creds, hash_with_creds, - "URLs with no password, though with a username, should hash the same as URLs with different user credentials", - ); - - Ok(()) - } - - #[test] - fn canonical_url() -> Result<(), url::ParseError> { - // Two URLs should be considered equal regardless of the `.git` suffix. - assert_eq!( - CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?, - CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages")?, - ); - - // Two URLs should be considered equal regardless of the `.git` suffix. - assert_eq!( - CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git@2.0.0")?, - CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages@2.0.0")?, - ); - - // Two URLs should be _not_ considered equal if they point to different repositories. - assert_ne!( - CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?, - CanonicalUrl::parse("git+https://github.com/pypa/sample-packages.git")?, - ); - - // Two URLs should _not_ be considered equal if they request different subdirectories. - assert_ne!( - CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git#subdirectory=pkg_resources/pkg_a")?, - CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git#subdirectory=pkg_resources/pkg_b")?, - ); - - // Two URLs should _not_ be considered equal if they request different commit tags. - assert_ne!( - CanonicalUrl::parse( - "git+https://github.com/pypa/sample-namespace-packages.git@v1.0.0" - )?, - CanonicalUrl::parse( - "git+https://github.com/pypa/sample-namespace-packages.git@v2.0.0" - )?, - ); - - // Two URLs that cannot be a base should be considered equal. - assert_eq!( - CanonicalUrl::parse("git+https:://github.com/pypa/sample-namespace-packages.git")?, - CanonicalUrl::parse("git+https:://github.com/pypa/sample-namespace-packages.git")?, - ); - - Ok(()) - } - - #[test] - fn repository_url() -> Result<(), url::ParseError> { - // Two URLs should be considered equal regardless of the `.git` suffix. - assert_eq!( - RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?, - RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages")?, - ); - - // Two URLs should be considered equal regardless of the `.git` suffix. - assert_eq!( - RepositoryUrl::parse( - "git+https://github.com/pypa/sample-namespace-packages.git@2.0.0" - )?, - RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages@2.0.0")?, - ); - - // Two URLs should be _not_ considered equal if they point to different repositories. - assert_ne!( - RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?, - RepositoryUrl::parse("git+https://github.com/pypa/sample-packages.git")?, - ); - - // Two URLs should be considered equal if they map to the same repository, even if they - // request different subdirectories. - assert_eq!( - RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git#subdirectory=pkg_resources/pkg_a")?, - RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git#subdirectory=pkg_resources/pkg_b")?, - ); - - // Two URLs should be considered equal if they map to the same repository, even if they - // request different commit tags. - assert_eq!( - RepositoryUrl::parse( - "git+https://github.com/pypa/sample-namespace-packages.git@v1.0.0" - )?, - RepositoryUrl::parse( - "git+https://github.com/pypa/sample-namespace-packages.git@v2.0.0" - )?, - ); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-cache-key/src/canonical_url/tests.rs b/crates/uv-cache-key/src/canonical_url/tests.rs new file mode 100644 index 000000000..0f6d15788 --- /dev/null +++ b/crates/uv-cache-key/src/canonical_url/tests.rs @@ -0,0 +1,125 @@ +use super::*; + +#[test] +fn user_credential_does_not_affect_cache_key() -> Result<(), url::ParseError> { + let mut hasher = CacheKeyHasher::new(); + CanonicalUrl::parse("https://example.com/pypa/sample-namespace-packages.git@2.0.0")? + .cache_key(&mut hasher); + let hash_without_creds = hasher.finish(); + + let mut hasher = CacheKeyHasher::new(); + CanonicalUrl::parse("https://user:foo@example.com/pypa/sample-namespace-packages.git@2.0.0")? + .cache_key(&mut hasher); + let hash_with_creds = hasher.finish(); + assert_eq!( + hash_without_creds, hash_with_creds, + "URLs with no user credentials should hash the same as URLs with different user credentials", + ); + + let mut hasher = CacheKeyHasher::new(); + CanonicalUrl::parse("https://user:bar@example.com/pypa/sample-namespace-packages.git@2.0.0")? + .cache_key(&mut hasher); + let hash_with_creds = hasher.finish(); + assert_eq!( + hash_without_creds, hash_with_creds, + "URLs with different user credentials should hash the same", + ); + + let mut hasher = CacheKeyHasher::new(); + CanonicalUrl::parse("https://:bar@example.com/pypa/sample-namespace-packages.git@2.0.0")? + .cache_key(&mut hasher); + let hash_with_creds = hasher.finish(); + assert_eq!( + hash_without_creds, hash_with_creds, + "URLs with no username, though with a password, should hash the same as URLs with different user credentials", + ); + + let mut hasher = CacheKeyHasher::new(); + CanonicalUrl::parse("https://user:@example.com/pypa/sample-namespace-packages.git@2.0.0")? + .cache_key(&mut hasher); + let hash_with_creds = hasher.finish(); + assert_eq!( + hash_without_creds, hash_with_creds, + "URLs with no password, though with a username, should hash the same as URLs with different user credentials", + ); + + Ok(()) +} + +#[test] +fn canonical_url() -> Result<(), url::ParseError> { + // Two URLs should be considered equal regardless of the `.git` suffix. + assert_eq!( + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?, + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages")?, + ); + + // Two URLs should be considered equal regardless of the `.git` suffix. + assert_eq!( + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git@2.0.0")?, + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages@2.0.0")?, + ); + + // Two URLs should be _not_ considered equal if they point to different repositories. + assert_ne!( + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?, + CanonicalUrl::parse("git+https://github.com/pypa/sample-packages.git")?, + ); + + // Two URLs should _not_ be considered equal if they request different subdirectories. + assert_ne!( + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git#subdirectory=pkg_resources/pkg_a")?, + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git#subdirectory=pkg_resources/pkg_b")?, + ); + + // Two URLs should _not_ be considered equal if they request different commit tags. + assert_ne!( + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git@v1.0.0")?, + CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git@v2.0.0")?, + ); + + // Two URLs that cannot be a base should be considered equal. + assert_eq!( + CanonicalUrl::parse("git+https:://github.com/pypa/sample-namespace-packages.git")?, + CanonicalUrl::parse("git+https:://github.com/pypa/sample-namespace-packages.git")?, + ); + + Ok(()) +} + +#[test] +fn repository_url() -> Result<(), url::ParseError> { + // Two URLs should be considered equal regardless of the `.git` suffix. + assert_eq!( + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?, + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages")?, + ); + + // Two URLs should be considered equal regardless of the `.git` suffix. + assert_eq!( + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git@2.0.0")?, + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages@2.0.0")?, + ); + + // Two URLs should be _not_ considered equal if they point to different repositories. + assert_ne!( + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?, + RepositoryUrl::parse("git+https://github.com/pypa/sample-packages.git")?, + ); + + // Two URLs should be considered equal if they map to the same repository, even if they + // request different subdirectories. + assert_eq!( + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git#subdirectory=pkg_resources/pkg_a")?, + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git#subdirectory=pkg_resources/pkg_b")?, + ); + + // Two URLs should be considered equal if they map to the same repository, even if they + // request different commit tags. + assert_eq!( + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git@v1.0.0")?, + RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git@v2.0.0")?, + ); + + Ok(()) +} diff --git a/crates/uv-cache/Cargo.toml b/crates/uv-cache/Cargo.toml index dab67387c..8f33d81f0 100644 --- a/crates/uv-cache/Cargo.toml +++ b/crates/uv-cache/Cargo.toml @@ -10,6 +10,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -20,6 +23,7 @@ uv-distribution-types = { workspace = true } uv-fs = { workspace = true, features = ["tokio"] } uv-normalize = { workspace = true } uv-pypi-types = { workspace = true } +uv-static = { workspace = true } clap = { workspace = true, features = ["derive", "env"], optional = true } directories = { workspace = true } diff --git a/crates/uv-cache/src/cli.rs b/crates/uv-cache/src/cli.rs index 06f0eca93..781b74017 100644 --- a/crates/uv-cache/src/cli.rs +++ b/crates/uv-cache/src/cli.rs @@ -1,5 +1,6 @@ use std::io; use std::path::{Path, PathBuf}; +use uv_static::EnvVars; use crate::Cache; use clap::Parser; @@ -17,7 +18,7 @@ pub struct CacheArgs { long, short, alias = "no-cache-dir", - env = "UV_NO_CACHE", + env = EnvVars::UV_NO_CACHE, value_parser = clap::builder::BoolishValueParser::new(), )] pub no_cache: bool, @@ -26,7 +27,7 @@ pub struct CacheArgs { /// /// Defaults to `$HOME/Library/Caches/uv` on macOS, `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on /// Linux, and `%LOCALAPPDATA%\uv\cache` on Windows. - #[arg(global = true, long, env = "UV_CACHE_DIR")] + #[arg(global = true, long, env = EnvVars::UV_CACHE_DIR)] pub cache_dir: Option, } diff --git a/crates/uv-cache/src/lib.rs b/crates/uv-cache/src/lib.rs index 3c3f8e01e..a91b9cdef 100644 --- a/crates/uv-cache/src/lib.rs +++ b/crates/uv-cache/src/lib.rs @@ -631,7 +631,7 @@ pub enum CacheBucket { /// can put next to the wheels as in the `Wheels` bucket. /// /// The unzipped source distribution is stored in a directory matching the source distribution - /// acrhive name. + /// archive name. /// /// Source distributions are built into zipped wheel files (as PEP 517 specifies) and unzipped /// lazily before installing. So when resolving, we only build the wheel and store the archive diff --git a/crates/uv-cli/Cargo.toml b/crates/uv-cli/Cargo.toml index 1414e429d..8dfc57769 100644 --- a/crates/uv-cli/Cargo.toml +++ b/crates/uv-cli/Cargo.toml @@ -10,6 +10,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -24,6 +27,7 @@ uv-pypi-types = { workspace = true } uv-python = { workspace = true, features = ["clap", "schemars"]} uv-resolver = { workspace = true, features = ["clap"] } uv-settings = { workspace = true, features = ["schemars"] } +uv-static = { workspace = true } uv-version = { workspace = true } uv-warnings = { workspace = true } @@ -42,4 +46,5 @@ default = [] self-update = [] [build-dependencies] +uv-static = { workspace = true } fs-err = { workspace = true } diff --git a/crates/uv-cli/build.rs b/crates/uv-cli/build.rs index bf4b6b77a..26bcdcf9e 100644 --- a/crates/uv-cli/build.rs +++ b/crates/uv-cli/build.rs @@ -5,10 +5,12 @@ use std::{ use fs_err as fs; +use uv_static::EnvVars; + fn main() { // The workspace root directory is not available without walking up the tree // https://github.com/rust-lang/cargo/issues/3946 - let workspace_root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) + let workspace_root = Path::new(&std::env::var(EnvVars::CARGO_MANIFEST_DIR).unwrap()) .parent() .expect("CARGO_MANIFEST_DIR should be nested in workspace") .parent() @@ -18,7 +20,7 @@ fn main() { commit_info(&workspace_root); #[allow(clippy::disallowed_methods)] - let target = std::env::var("TARGET").unwrap(); + let target = std::env::var(EnvVars::TARGET).unwrap(); println!("cargo:rustc-env=RUST_HOST_TARGET={target}"); } @@ -62,21 +64,27 @@ fn commit_info(workspace_root: &Path) { let stdout = String::from_utf8(output.stdout).unwrap(); let mut parts = stdout.split_whitespace(); let mut next = || parts.next().unwrap(); - println!("cargo:rustc-env=UV_COMMIT_HASH={}", next()); - println!("cargo:rustc-env=UV_COMMIT_SHORT_HASH={}", next()); - println!("cargo:rustc-env=UV_COMMIT_DATE={}", next()); + println!("cargo:rustc-env={}={}", EnvVars::UV_COMMIT_HASH, next()); + println!( + "cargo:rustc-env={}={}", + EnvVars::UV_COMMIT_SHORT_HASH, + next() + ); + println!("cargo:rustc-env={}={}", EnvVars::UV_COMMIT_DATE, next()); // Describe can fail for some commits // https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emdescribeoptionsem if let Some(describe) = parts.next() { let mut describe_parts = describe.split('-'); println!( - "cargo:rustc-env=UV_LAST_TAG={}", + "cargo:rustc-env={}={}", + EnvVars::UV_LAST_TAG, describe_parts.next().unwrap() ); // If this is the tagged commit, this component will be missing println!( - "cargo:rustc-env=UV_LAST_TAG_DISTANCE={}", + "cargo:rustc-env={}={}", + EnvVars::UV_LAST_TAG_DISTANCE, describe_parts.next().unwrap_or("0") ); } diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index ac3704ec8..0177af3e7 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1,5 +1,5 @@ use std::ffi::OsString; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use std::str::FromStr; @@ -7,18 +7,20 @@ use anyhow::{anyhow, Result}; use clap::builder::styling::{AnsiColor, Effects, Style}; use clap::builder::Styles; use clap::{Args, Parser, Subcommand}; + use url::Url; use uv_cache::CacheArgs; use uv_configuration::{ ConfigSettingEntry, ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier, - TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem, + ProjectBuildBackend, TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem, }; -use uv_distribution_types::{FlatIndexLocation, IndexUrl}; +use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex}; use uv_normalize::{ExtraName, PackageName}; use uv_pep508::Requirement; use uv_pypi_types::VerbatimParsedUrl; use uv_python::{PythonDownloads, PythonPreference, PythonVersion}; use uv_resolver::{AnnotationStyle, ExcludeNewer, PrereleaseMode, ResolutionMode}; +use uv_static::EnvVars; pub mod compat; pub mod options; @@ -97,7 +99,7 @@ pub struct TopLevelArgs { #[arg( global = true, long, - env = "UV_CONFIG_FILE", + env = EnvVars::UV_CONFIG_FILE, help_heading = "Global options" )] pub config_file: Option, @@ -106,7 +108,7 @@ pub struct TopLevelArgs { /// /// Normally, configuration files are discovered in the current directory, /// parent directories, or user configuration directories. - #[arg(global = true, long, env = "UV_NO_CONFIG", value_parser = clap::builder::BoolishValueParser::new(), help_heading = "Global options")] + #[arg(global = true, long, env = EnvVars::UV_NO_CONFIG, value_parser = clap::builder::BoolishValueParser::new(), help_heading = "Global options")] pub no_config: bool, /// Display the concise help for this command. @@ -133,7 +135,7 @@ pub struct GlobalArgs { long, help_heading = "Python options", display_order = 700, - env = "UV_PYTHON_PREFERENCE" + env = EnvVars::UV_PYTHON_PREFERENCE )] pub python_preference: Option, @@ -188,7 +190,7 @@ pub struct GlobalArgs { /// However, in some cases, you may want to use the platform's native certificate store, /// especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's /// included in your system's certificate store. - #[arg(global = true, long, env = "UV_NATIVE_TLS", value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_native_tls"))] + #[arg(global = true, long, env = EnvVars::UV_NATIVE_TLS, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_native_tls"))] pub native_tls: bool, #[arg(global = true, long, overrides_with("native_tls"), hide = true)] @@ -206,7 +208,7 @@ pub struct GlobalArgs { /// Whether to enable experimental, preview features. /// /// Preview features may change without warning. - #[arg(global = true, long, hide = true, env = "UV_PREVIEW", value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_preview"))] + #[arg(global = true, long, hide = true, env = EnvVars::UV_PREVIEW, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_preview"))] pub preview: bool, #[arg(global = true, long, overrides_with("preview"), hide = true)] @@ -468,7 +470,7 @@ pub struct SelfUpdateArgs { /// A GitHub token for authentication. /// A token is not required but can be used to reduce the chance of encountering rate limits. - #[arg(long, env = "UV_GITHUB_TOKEN")] + #[arg(long, env = EnvVars::UV_GITHUB_TOKEN)] pub token: Option, } @@ -600,12 +602,13 @@ pub enum ProjectCommand { /// /// Ensures that the command runs in a Python environment. /// - /// When used with a file ending in `.py`, the file will be treated as a - /// script and run with a Python interpreter, i.e., `uv run file.py` is - /// equivalent to `uv run python file.py`. If the script contains inline - /// dependency metadata, it will be installed into an isolated, ephemeral - /// environment. When used with `-`, the input will be read from stdin, - /// and treated as a Python script. + /// When used with a file ending in `.py` or an HTTP(S) URL, the file + /// will be treated as a script and run with a Python interpreter, + /// i.e., `uv run file.py` is equivalent to `uv run python file.py`. + /// For URLs, the script is temporarily downloaded before execution. If + /// the script contains inline dependency metadata, it will be installed + /// into an isolated, ephemeral environment. When used with `-`, the + /// input will be read from stdin, and treated as a Python script. /// /// When used in a project, the project environment will be created and /// updated before invoking the command. @@ -765,27 +768,90 @@ impl Maybe { Maybe::None => None, } } + + pub fn is_some(&self) -> bool { + matches!(self, Maybe::Some(_)) + } } -/// Parse a string into an [`IndexUrl`], mapping the empty string to `None`. -fn parse_index_url(input: &str) -> Result, String> { +/// Parse an `--index-url` argument into an [`PipIndex`], mapping the empty string to `None`. +fn parse_index_url(input: &str) -> Result, String> { if input.is_empty() { Ok(Maybe::None) } else { - match IndexUrl::from_str(input) { - Ok(url) => Ok(Maybe::Some(url)), + IndexUrl::from_str(input) + .map(Index::from_index_url) + .map(|index| Index { + origin: Some(Origin::Cli), + ..index + }) + .map(PipIndex::from) + .map(Maybe::Some) + .map_err(|err| err.to_string()) + } +} + +/// Parse an `--extra-index-url` argument into an [`PipExtraIndex`], mapping the empty string to `None`. +fn parse_extra_index_url(input: &str) -> Result, String> { + if input.is_empty() { + Ok(Maybe::None) + } else { + IndexUrl::from_str(input) + .map(Index::from_extra_index_url) + .map(|index| Index { + origin: Some(Origin::Cli), + ..index + }) + .map(PipExtraIndex::from) + .map(Maybe::Some) + .map_err(|err| err.to_string()) + } +} + +/// Parse a `--find-links` argument into an [`PipFindLinks`], mapping the empty string to `None`. +fn parse_find_links(input: &str) -> Result, String> { + if input.is_empty() { + Ok(Maybe::None) + } else { + IndexUrl::from_str(input) + .map(Index::from_find_links) + .map(|index| Index { + origin: Some(Origin::Cli), + ..index + }) + .map(PipFindLinks::from) + .map(Maybe::Some) + .map_err(|err| err.to_string()) + } +} + +/// Parse an `--index` argument into an [`Index`], mapping the empty string to `None`. +fn parse_index(input: &str) -> Result, String> { + if input.is_empty() { + Ok(Maybe::None) + } else { + match Index::from_str(input) { + Ok(index) => Ok(Maybe::Some(Index { + default: false, + origin: Some(Origin::Cli), + ..index + })), Err(err) => Err(err.to_string()), } } } -/// Parse a string into an [`FlatIndexLocation`], mapping the empty string to `None`. -fn parse_flat_index(input: &str) -> Result, String> { +/// Parse a `--default-index` argument into an [`Index`], mapping the empty string to `None`. +fn parse_default_index(input: &str) -> Result, String> { if input.is_empty() { Ok(Maybe::None) } else { - match FlatIndexLocation::from_str(input) { - Ok(url) => Ok(Maybe::Some(url)), + match Index::from_str(input) { + Ok(index) => Ok(Maybe::Some(Index { + default: true, + origin: Some(Origin::Cli), + ..index + })), Err(err) => Err(err.to_string()), } } @@ -859,7 +925,7 @@ pub struct PipCompileArgs { /// trigger the installation of that package. /// /// This is equivalent to pip's `--constraint` option. - #[arg(long, short, env = "UV_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, short, env = EnvVars::UV_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub constraint: Vec>, /// Override versions using the given requirements files. @@ -871,7 +937,7 @@ pub struct PipCompileArgs { /// While constraints are _additive_, in that they're combined with the requirements of the /// constituent packages, overrides are _absolute_, in that they completely replace the /// requirements of the constituent packages. - #[arg(long, env = "UV_OVERRIDE", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, env = EnvVars::UV_OVERRIDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub r#override: Vec>, /// Constrain build dependencies using the given requirements files when building source @@ -880,7 +946,7 @@ pub struct PipCompileArgs { /// Constraints files are `requirements.txt`-like files that only control the _version_ of a /// requirement that's installed. However, including a package in a constraints file will _not_ /// trigger the installation of that package. - #[arg(long, short, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, short, env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub build_constraint: Vec>, /// Include optional dependencies from the extra group name; may be provided more than once. @@ -964,7 +1030,7 @@ pub struct PipCompileArgs { /// The header comment to include at the top of the output file generated by `uv pip compile`. /// /// Used to reflect custom build scripts and commands that wrap `uv pip compile`. - #[arg(long, env = "UV_CUSTOM_COMPILE_COMMAND")] + #[arg(long, env = EnvVars::UV_CUSTOM_COMPILE_COMMAND)] pub custom_compile_command: Option, /// The Python interpreter to use during resolution. @@ -988,7 +1054,7 @@ pub struct PipCompileArgs { /// the system path. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -1160,7 +1226,7 @@ pub struct PipSyncArgs { /// trigger the installation of that package. /// /// This is equivalent to pip's `--constraint` option. - #[arg(long, short, env = "UV_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, short, env = EnvVars::UV_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub constraint: Vec>, /// Constrain build dependencies using the given requirements files when building source @@ -1169,7 +1235,7 @@ pub struct PipSyncArgs { /// Constraints files are `requirements.txt`-like files that only control the _version_ of a /// requirement that's installed. However, including a package in a constraints file will _not_ /// trigger the installation of that package. - #[arg(long, short, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, short, env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub build_constraint: Vec>, #[command(flatten)] @@ -1192,7 +1258,7 @@ pub struct PipSyncArgs { /// source archive (`.zip`, `.tar.gz`), as opposed to a directory. #[arg( long, - env = "UV_REQUIRE_HASHES", + env = EnvVars::UV_REQUIRE_HASHES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_require_hashes"), )] @@ -1208,7 +1274,7 @@ pub struct PipSyncArgs { /// include them. #[arg( long, - env = "UV_VERIFY_HASHES", + env = EnvVars::UV_VERIFY_HASHES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_verify_hashes"), )] @@ -1229,7 +1295,7 @@ pub struct PipSyncArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -1246,7 +1312,7 @@ pub struct PipSyncArgs { /// should be used with caution, as it can modify the system Python installation. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -1263,7 +1329,7 @@ pub struct PipSyncArgs { /// explicitly recommend against modifications by other package managers (like uv or `pip`). #[arg( long, - env = "UV_BREAK_SYSTEM_PACKAGES", + env = EnvVars::UV_BREAK_SYSTEM_PACKAGES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_break_system_packages") )] @@ -1408,7 +1474,7 @@ pub struct PipInstallArgs { /// trigger the installation of that package. /// /// This is equivalent to pip's `--constraint` option. - #[arg(long, short, env = "UV_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, short, env = EnvVars::UV_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub constraint: Vec>, /// Override versions using the given requirements files. @@ -1420,7 +1486,7 @@ pub struct PipInstallArgs { /// While constraints are _additive_, in that they're combined with the requirements of the /// constituent packages, overrides are _absolute_, in that they completely replace the /// requirements of the constituent packages. - #[arg(long, env = "UV_OVERRIDE", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, env = EnvVars::UV_OVERRIDE, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub r#override: Vec>, /// Constrain build dependencies using the given requirements files when building source @@ -1429,7 +1495,7 @@ pub struct PipInstallArgs { /// Constraints files are `requirements.txt`-like files that only control the _version_ of a /// requirement that's installed. However, including a package in a constraints file will _not_ /// trigger the installation of that package. - #[arg(long, short, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, short, env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub build_constraint: Vec>, /// Include optional dependencies from the extra group name; may be provided more than once. @@ -1475,7 +1541,7 @@ pub struct PipInstallArgs { /// source archive (`.zip`, `.tar.gz`), as opposed to a directory. #[arg( long, - env = "UV_REQUIRE_HASHES", + env = EnvVars::UV_REQUIRE_HASHES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_require_hashes"), )] @@ -1491,7 +1557,7 @@ pub struct PipInstallArgs { /// include them. #[arg( long, - env = "UV_VERIFY_HASHES", + env = EnvVars::UV_VERIFY_HASHES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_verify_hashes"), )] @@ -1512,7 +1578,7 @@ pub struct PipInstallArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -1529,7 +1595,7 @@ pub struct PipInstallArgs { /// should be used with caution, as it can modify the system Python installation. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -1546,7 +1612,7 @@ pub struct PipInstallArgs { /// explicitly recommend against modifications by other package managers (like uv or `pip`). #[arg( long, - env = "UV_BREAK_SYSTEM_PACKAGES", + env = EnvVars::UV_BREAK_SYSTEM_PACKAGES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_break_system_packages") )] @@ -1637,6 +1703,18 @@ pub struct PipInstallArgs { #[arg(long)] pub python_platform: Option, + /// Do not remove extraneous packages present in the environment. + #[arg(long, overrides_with("exact"), alias = "no-exact", hide = true)] + pub inexact: bool, + + /// Perform an exact sync, removing extraneous packages. + /// + /// By default, installing will make the minimum necessary changes to satisfy the requirements. + /// When enabled, uv will update the environment to exactly match the requirements, removing + /// packages that are not included in the requirements. + #[arg(long, overrides_with("inexact"))] + pub exact: bool, + /// Validate the Python environment after completing the installation, to detect and with /// missing dependencies or other issues. #[arg(long, overrides_with("no_strict"))] @@ -1678,7 +1756,7 @@ pub struct PipUninstallArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -1691,7 +1769,7 @@ pub struct PipUninstallArgs { /// use the `keyring` CLI to handle authentication. /// /// Defaults to `disabled`. - #[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")] + #[arg(long, value_enum, env = EnvVars::UV_KEYRING_PROVIDER)] pub keyring_provider: Option, /// Allow insecure connections to a host. @@ -1707,7 +1785,7 @@ pub struct PipUninstallArgs { #[arg( long, alias = "trusted-host", - env = "UV_INSECURE_HOST", + env = EnvVars::UV_INSECURE_HOST, value_delimiter = ' ', value_parser = parse_insecure_host, )] @@ -1723,7 +1801,7 @@ pub struct PipUninstallArgs { /// should be used with caution, as it can modify the system Python installation. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -1740,7 +1818,7 @@ pub struct PipUninstallArgs { /// explicitly recommend against modifications by other package managers (like uv or `pip`). #[arg( long, - env = "UV_BREAK_SYSTEM_PACKAGES", + env = EnvVars::UV_BREAK_SYSTEM_PACKAGES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_break_system_packages") )] @@ -1787,7 +1865,7 @@ pub struct PipFreezeArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -1801,7 +1879,7 @@ pub struct PipFreezeArgs { /// See `uv help python` for details on Python discovery. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -1852,7 +1930,7 @@ pub struct PipListArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -1866,7 +1944,7 @@ pub struct PipListArgs { /// See `uv help python` for details on Python discovery. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -1893,7 +1971,7 @@ pub struct PipCheckArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -1907,7 +1985,7 @@ pub struct PipCheckArgs { /// See `uv help python` for details on Python discovery. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -1942,7 +2020,7 @@ pub struct PipShowArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -1956,7 +2034,7 @@ pub struct PipShowArgs { /// See `uv help python` for details on Python discovery. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -1998,7 +2076,7 @@ pub struct PipTreeArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -2012,7 +2090,7 @@ pub struct PipTreeArgs { /// See `uv help python` for details on Python discovery. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -2081,7 +2159,7 @@ pub struct BuildArgs { /// Constraints files are `requirements.txt`-like files that only control the _version_ of a /// build dependency that's installed. However, including a package in a constraints file will /// _not_ trigger the inclusion of that package on its own. - #[arg(long, short, env = "UV_BUILD_CONSTRAINT", value_delimiter = ' ', value_parser = parse_maybe_file_path)] + #[arg(long, short, env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub build_constraint: Vec>, /// Require a matching hash for each build requirement. @@ -2099,7 +2177,7 @@ pub struct BuildArgs { /// source archive (`.zip`, `.tar.gz`), as opposed to a directory. #[arg( long, - env = "UV_REQUIRE_HASHES", + env = EnvVars::UV_REQUIRE_HASHES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_require_hashes"), )] @@ -2115,7 +2193,7 @@ pub struct BuildArgs { /// include them. #[arg( long, - env = "UV_VERIFY_HASHES", + env = EnvVars::UV_VERIFY_HASHES, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_verify_hashes"), )] @@ -2134,7 +2212,7 @@ pub struct BuildArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -2164,7 +2242,7 @@ pub struct VenvArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -2176,7 +2254,7 @@ pub struct VenvArgs { /// This is the default behavior and has no effect. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system"), hide = true, @@ -2268,9 +2346,9 @@ pub struct VenvArgs { /// /// By default, uv will stop at the first index on which a given package is available, and /// limit resolutions to those present on that first index (`first-match`). This prevents - /// "dependency confusion" attacks, whereby an attack can upload a malicious package under the - /// same name to a secondary. - #[arg(long, value_enum, env = "UV_INDEX_STRATEGY")] + /// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the + /// same name to an alternate index. + #[arg(long, value_enum, env = EnvVars::UV_INDEX_STRATEGY)] pub index_strategy: Option, /// Attempt to use `keyring` for authentication for index URLs. @@ -2279,7 +2357,7 @@ pub struct VenvArgs { /// use the `keyring` CLI to handle authentication. /// /// Defaults to `disabled`. - #[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")] + #[arg(long, value_enum, env = EnvVars::UV_KEYRING_PROVIDER)] pub keyring_provider: Option, /// Allow insecure connections to a host. @@ -2295,7 +2373,7 @@ pub struct VenvArgs { #[arg( long, alias = "trusted-host", - env = "UV_INSECURE_HOST", + env = EnvVars::UV_INSECURE_HOST, value_delimiter = ' ', value_parser = parse_insecure_host, )] @@ -2305,7 +2383,7 @@ pub struct VenvArgs { /// /// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same /// format (e.g., `2006-12-02`) in your system's configured time zone. - #[arg(long, env = "UV_EXCLUDE_NEWER")] + #[arg(long, env = EnvVars::UV_EXCLUDE_NEWER)] pub exclude_newer: Option, /// The method to use when installing packages from the global cache. @@ -2314,7 +2392,7 @@ pub struct VenvArgs { /// /// Defaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and /// Windows. - #[arg(long, value_enum, env = "UV_LINK_MODE")] + #[arg(long, value_enum, env = EnvVars::UV_LINK_MODE)] pub link_mode: Option, #[command(flatten)] @@ -2337,6 +2415,14 @@ impl Deref for ExternalCommand { } } +impl DerefMut for ExternalCommand { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Self::Cmd(cmd) => cmd, + } + } +} + impl ExternalCommand { pub fn split(&self) -> (Option<&OsString>, &[OsString]) { match self.as_slice() { @@ -2346,6 +2432,17 @@ impl ExternalCommand { } } +#[derive(Debug, Default, Copy, Clone, clap::ValueEnum)] +pub enum AuthorFrom { + /// Fetch the author information from some sources (e.g., Git) automatically. + #[default] + Auto, + /// Fetch the author information from Git configuration only. + Git, + /// Do not infer the author information. + None, +} + #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct InitArgs { @@ -2428,10 +2525,22 @@ pub struct InitArgs { #[arg(long, value_enum, conflicts_with = "script")] pub vcs: Option, + /// Initialize a build-backend of choice for the project. + #[arg(long, value_enum, conflicts_with_all=["script", "no_package"])] + pub build_backend: Option, + /// Do not create a `README.md` file. #[arg(long)] pub no_readme: bool, + /// Fill in the `authors` field in the `pyproject.toml`. + /// + /// By default, uv will attempt to infer the author information from some sources (e.g., Git) (`auto`). + /// Use `--author-from git` to only infer from Git configuration. + /// Use `--author-from none` to avoid inferring the author information. + #[arg(long, value_enum)] + pub author_from: Option, + /// Do not create a `.python-version` file for the project. /// /// By default, uv will create a `.python-version` file containing the minor version of @@ -2453,7 +2562,7 @@ pub struct InitArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -2524,14 +2633,14 @@ pub struct RunArgs { /// If the path to a Python script (i.e., ending in `.py`), it will be /// executed with the Python interpreter. #[command(subcommand)] - pub command: ExternalCommand, + pub command: Option, /// Run with the given packages installed. /// /// When used in a project, these dependencies will be layered on top of /// the project environment in a separate, ephemeral environment. These /// dependencies are allowed to conflict with those specified by the project. - #[arg(long)] + #[arg(long, value_delimiter = ',')] pub with: Vec, /// Run with the given packages installed as editables. @@ -2539,7 +2648,7 @@ pub struct RunArgs { /// When used in a project, these dependencies will be layered on top of /// the project environment in a separate, ephemeral environment. These /// dependencies are allowed to conflict with those specified by the project. - #[arg(long)] + #[arg(long, value_delimiter = ',')] pub with_editable: Vec, /// Run with all packages listed in the given `requirements.txt` files. @@ -2547,7 +2656,7 @@ pub struct RunArgs { /// The same environment semantics as `--with` apply. /// /// Using `pyproject.toml`, `setup.py`, or `setup.cfg` files is not allowed. - #[arg(long, value_parser = parse_maybe_file_path)] + #[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)] pub with_requirements: Vec>, /// Run the command in an isolated virtual environment. @@ -2567,7 +2676,7 @@ pub struct RunArgs { /// /// Implies `--frozen`, as the project dependencies will be ignored (i.e., the lockfile will not /// be updated, since the environment will not be synced regardless). - #[arg(long, env = "UV_NO_SYNC", value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")] + #[arg(long, env = EnvVars::UV_NO_SYNC, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")] pub no_sync: bool, /// Assert that the `uv.lock` will remain unchanged. @@ -2629,7 +2738,7 @@ pub struct RunArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -2639,7 +2748,7 @@ pub struct RunArgs { /// Whether to show resolver and installer output from any environment modifications. /// /// By default, environment modifications are omitted, but enabled under `--verbose`. - #[arg(long, env = "UV_SHOW_RESOLUTION", value_parser = clap::builder::BoolishValueParser::new(), hide = true)] + #[arg(long, env = EnvVars::UV_SHOW_RESOLUTION, value_parser = clap::builder::BoolishValueParser::new(), hide = true)] pub show_resolution: bool, } @@ -2772,7 +2881,7 @@ pub struct SyncArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -2816,7 +2925,7 @@ pub struct LockArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -2893,7 +3002,7 @@ pub struct AddArgs { pub extra: Option>, /// Avoid syncing the virtual environment. - #[arg(long, env = "UV_NO_SYNC", value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")] + #[arg(long, env = EnvVars::UV_NO_SYNC, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")] pub no_sync: bool, /// Assert that the `uv.lock` will remain unchanged. @@ -2939,7 +3048,7 @@ pub struct AddArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -2963,7 +3072,7 @@ pub struct RemoveArgs { pub optional: Option, /// Avoid syncing the virtual environment after re-locking the project. - #[arg(long, env = "UV_NO_SYNC", value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")] + #[arg(long, env = EnvVars::UV_NO_SYNC, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")] pub no_sync: bool, /// Assert that the `uv.lock` will remain unchanged. @@ -3006,7 +3115,7 @@ pub struct RemoveArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -3030,6 +3139,17 @@ pub struct TreeArgs { #[command(flatten)] pub tree: DisplayTreeArgs, + /// Include development dependencies. + /// + /// Development dependencies are defined via `tool.uv.dev-dependencies` in a + /// `pyproject.toml`. + #[arg(long, overrides_with("no_dev"), hide = true)] + pub dev: bool, + + /// Omit development dependencies. + #[arg(long, overrides_with("dev"), conflicts_with = "invert")] + pub no_dev: bool, + /// Assert that the `uv.lock` will remain unchanged. /// /// Requires that the lockfile is up-to-date. If the lockfile is missing or @@ -3081,7 +3201,7 @@ pub struct TreeArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -3131,6 +3251,13 @@ pub struct ExportArgs { #[arg(long, conflicts_with("no_dev"))] pub only_dev: bool, + /// Exclude the comment header at the top of the generated output file. + #[arg(long, overrides_with("header"))] + pub no_header: bool, + + #[arg(long, overrides_with("no_header"), hide = true)] + pub header: bool, + /// Install any editable dependencies, including the project and any workspace members, as /// non-editable. #[arg(long)] @@ -3206,7 +3333,7 @@ pub struct ExportArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -3243,6 +3370,11 @@ pub enum ToolCommand { /// /// Packages are installed into an ephemeral virtual environment in the uv /// cache directory. + #[command( + after_help = "Use `uvx` as a shortcut for `uv tool run`.\n\n\ + Use `uv help tool run` for more details.", + after_long_help = "" + )] Run(ToolRunArgs), /// Hidden alias for `uv tool run` for the `uvx` command #[command( @@ -3321,7 +3453,7 @@ pub struct ToolRunArgs { pub from: Option, /// Run with the given packages installed. - #[arg(long)] + #[arg(long, value_delimiter = ',')] pub with: Vec, /// Run with the given packages installed as editables @@ -3329,11 +3461,11 @@ pub struct ToolRunArgs { /// When used in a project, these dependencies will be layered on top of /// the uv tool's environment in a separate, ephemeral environment. These /// dependencies are allowed to conflict with those specified. - #[arg(long)] + #[arg(long, value_delimiter = ',')] pub with_editable: Vec, /// Run with all packages listed in the given `requirements.txt` files. - #[arg(long, value_parser = parse_maybe_file_path)] + #[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)] pub with_requirements: Vec>, /// Run the tool in an isolated virtual environment, ignoring any already-installed tools. @@ -3356,7 +3488,7 @@ pub struct ToolRunArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -3366,7 +3498,7 @@ pub struct ToolRunArgs { /// Whether to show resolver and installer output from any environment modifications. /// /// By default, environment modifications are omitted, but enabled under `--verbose`. - #[arg(long, env = "UV_SHOW_RESOLUTION", value_parser = clap::builder::BoolishValueParser::new(), hide = true)] + #[arg(long, env = EnvVars::UV_SHOW_RESOLUTION, value_parser = clap::builder::BoolishValueParser::new(), hide = true)] pub show_resolution: bool, #[arg(long, hide = true)] @@ -3389,11 +3521,11 @@ pub struct ToolInstallArgs { pub from: Option, /// Include the following extra requirements. - #[arg(long)] + #[arg(long, value_delimiter = ',')] pub with: Vec, /// Run all requirements listed in the given `requirements.txt` files. - #[arg(long, value_parser = parse_maybe_file_path)] + #[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path)] pub with_requirements: Vec>, #[command(flatten)] @@ -3418,7 +3550,7 @@ pub struct ToolInstallArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -3495,7 +3627,7 @@ pub struct ToolUpgradeArgs { #[arg( long, short, - env = "UV_PYTHON", + env = EnvVars::UV_PYTHON, verbatim_doc_comment, help_heading = "Python options", value_parser = parse_maybe_string, @@ -3659,7 +3791,7 @@ pub struct PythonFindArgs { /// restrict its search to the system path. #[arg( long, - env = "UV_SYSTEM_PYTHON", + env = EnvVars::UV_SYSTEM_PYTHON, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_system") )] @@ -3744,17 +3876,38 @@ pub struct GenerateShellCompletionArgs { #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct IndexArgs { - /// The URL of the Python package index (by default: ). + /// The URLs to use when resolving dependencies, in addition to the default index. + /// + /// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local + /// directory laid out in the same format. + /// + /// All indexes provided via this flag take priority over the index specified by + /// `--default-index` (which defaults to PyPI). When multiple `--index` flags are + /// provided, earlier values take priority. + #[arg(long, env = EnvVars::UV_INDEX, value_delimiter = ' ', value_parser = parse_index, help_heading = "Index options")] + pub index: Option>>, + + /// The URL of the default package index (by default: ). + /// + /// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local + /// directory laid out in the same format. + /// + /// The index given by this flag is given lower priority than all other indexes specified via + /// the `--index` flag. + #[arg(long, env = EnvVars::UV_DEFAULT_INDEX, value_parser = parse_default_index, help_heading = "Index options")] + pub default_index: Option>, + + /// (Deprecated: use `--default-index` instead) The URL of the Python package index (by default: ). /// /// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local /// directory laid out in the same format. /// /// The index given by this flag is given lower priority than all other /// indexes specified via the `--extra-index-url` flag. - #[arg(long, short, env = "UV_INDEX_URL", value_parser = parse_index_url, help_heading = "Index options")] - pub index_url: Option>, + #[arg(long, short, env = EnvVars::UV_INDEX_URL, value_parser = parse_index_url, help_heading = "Index options")] + pub index_url: Option>, - /// Extra URLs of package indexes to use, in addition to `--index-url`. + /// (Deprecated: use `--index` instead) Extra URLs of package indexes to use, in addition to `--index-url`. /// /// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local /// directory laid out in the same format. @@ -3762,8 +3915,8 @@ pub struct IndexArgs { /// All indexes provided via this flag take priority over the index specified by /// `--index-url` (which defaults to PyPI). When multiple `--extra-index-url` flags are /// provided, earlier values take priority. - #[arg(long, env = "UV_EXTRA_INDEX_URL", value_delimiter = ' ', value_parser = parse_index_url, help_heading = "Index options")] - pub extra_index_url: Option>>, + #[arg(long, env = EnvVars::UV_EXTRA_INDEX_URL, value_delimiter = ' ', value_parser = parse_extra_index_url, help_heading = "Index options")] + pub extra_index_url: Option>>, /// Locations to search for candidate distributions, in addition to those found in the registry /// indexes. @@ -3776,12 +3929,11 @@ pub struct IndexArgs { #[arg( long, short, - env = "UV_FIND_LINKS", - value_delimiter = ' ', - value_parser = parse_flat_index, + env = EnvVars::UV_FIND_LINKS, + value_parser = parse_find_links, help_heading = "Index options" )] - pub find_links: Option>>, + pub find_links: Option>>, /// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those /// provided via `--find-links`. @@ -3892,12 +4044,12 @@ pub struct InstallerArgs { /// /// By default, uv will stop at the first index on which a given package is available, and /// limit resolutions to those present on that first index (`first-match`). This prevents - /// "dependency confusion" attacks, whereby an attack can upload a malicious package under the - /// same name to a secondary. + /// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the + /// same name to an alternate index. #[arg( long, value_enum, - env = "UV_INDEX_STRATEGY", + env = EnvVars::UV_INDEX_STRATEGY, help_heading = "Index options" )] pub index_strategy: Option, @@ -3911,7 +4063,7 @@ pub struct InstallerArgs { #[arg( long, value_enum, - env = "UV_KEYRING_PROVIDER", + env = EnvVars::UV_KEYRING_PROVIDER, help_heading = "Index options" )] pub keyring_provider: Option, @@ -3929,7 +4081,7 @@ pub struct InstallerArgs { #[arg( long, alias = "trusted-host", - env = "UV_INSECURE_HOST", + env = EnvVars::UV_INSECURE_HOST, value_delimiter = ' ', value_parser = parse_insecure_host, help_heading = "Index options" @@ -3952,7 +4104,7 @@ pub struct InstallerArgs { long, overrides_with("build_isolation"), help_heading = "Build options", - env = "UV_NO_BUILD_ISOLATION", + env = EnvVars::UV_NO_BUILD_ISOLATION, value_parser = clap::builder::BoolishValueParser::new(), )] pub no_build_isolation: bool, @@ -3969,7 +4121,7 @@ pub struct InstallerArgs { /// /// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same /// format (e.g., `2006-12-02`) in your system's configured time zone. - #[arg(long, env = "UV_EXCLUDE_NEWER", help_heading = "Resolver options")] + #[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")] pub exclude_newer: Option, /// The method to use when installing packages from the global cache. @@ -3979,7 +4131,7 @@ pub struct InstallerArgs { #[arg( long, value_enum, - env = "UV_LINK_MODE", + env = EnvVars::UV_LINK_MODE, help_heading = "Installer options" )] pub link_mode: Option, @@ -3999,7 +4151,7 @@ pub struct InstallerArgs { alias = "compile", overrides_with("no_compile_bytecode"), help_heading = "Installer options", - env = "UV_COMPILE_BYTECODE", + env = EnvVars::UV_COMPILE_BYTECODE, value_parser = clap::builder::BoolishValueParser::new(), )] pub compile_bytecode: bool, @@ -4054,12 +4206,12 @@ pub struct ResolverArgs { /// /// By default, uv will stop at the first index on which a given package is available, and /// limit resolutions to those present on that first index (`first-match`). This prevents - /// "dependency confusion" attacks, whereby an attack can upload a malicious package under the - /// same name to a secondary. + /// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the + /// same name to an alternate index. #[arg( long, value_enum, - env = "UV_INDEX_STRATEGY", + env = EnvVars::UV_INDEX_STRATEGY, help_heading = "Index options" )] pub index_strategy: Option, @@ -4073,7 +4225,7 @@ pub struct ResolverArgs { #[arg( long, value_enum, - env = "UV_KEYRING_PROVIDER", + env = EnvVars::UV_KEYRING_PROVIDER, help_heading = "Index options" )] pub keyring_provider: Option, @@ -4091,7 +4243,7 @@ pub struct ResolverArgs { #[arg( long, alias = "trusted-host", - env = "UV_INSECURE_HOST", + env = EnvVars::UV_INSECURE_HOST, value_delimiter = ' ', value_parser = parse_insecure_host, help_heading = "Index options" @@ -4105,7 +4257,7 @@ pub struct ResolverArgs { #[arg( long, value_enum, - env = "UV_RESOLUTION", + env = EnvVars::UV_RESOLUTION, help_heading = "Resolver options" )] pub resolution: Option, @@ -4118,7 +4270,7 @@ pub struct ResolverArgs { #[arg( long, value_enum, - env = "UV_PRERELEASE", + env = EnvVars::UV_PRERELEASE, help_heading = "Resolver options" )] pub prerelease: Option, @@ -4142,7 +4294,7 @@ pub struct ResolverArgs { long, overrides_with("build_isolation"), help_heading = "Build options", - env = "UV_NO_BUILD_ISOLATION", + env = EnvVars::UV_NO_BUILD_ISOLATION, value_parser = clap::builder::BoolishValueParser::new(), )] pub no_build_isolation: bool, @@ -4165,7 +4317,7 @@ pub struct ResolverArgs { /// /// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same /// format (e.g., `2006-12-02`) in your system's configured time zone. - #[arg(long, env = "UV_EXCLUDE_NEWER", help_heading = "Resolver options")] + #[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")] pub exclude_newer: Option, /// The method to use when installing packages from the global cache. @@ -4177,7 +4329,7 @@ pub struct ResolverArgs { #[arg( long, value_enum, - env = "UV_LINK_MODE", + env = EnvVars::UV_LINK_MODE, help_heading = "Installer options" )] pub link_mode: Option, @@ -4246,12 +4398,12 @@ pub struct ResolverInstallerArgs { /// /// By default, uv will stop at the first index on which a given package is available, and /// limit resolutions to those present on that first index (`first-match`). This prevents - /// "dependency confusion" attacks, whereby an attack can upload a malicious package under the - /// same name to a secondary. + /// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the + /// same name to an alternate index. #[arg( long, value_enum, - env = "UV_INDEX_STRATEGY", + env = EnvVars::UV_INDEX_STRATEGY, help_heading = "Index options" )] pub index_strategy: Option, @@ -4265,7 +4417,7 @@ pub struct ResolverInstallerArgs { #[arg( long, value_enum, - env = "UV_KEYRING_PROVIDER", + env = EnvVars::UV_KEYRING_PROVIDER, help_heading = "Index options" )] pub keyring_provider: Option, @@ -4283,7 +4435,7 @@ pub struct ResolverInstallerArgs { #[arg( long, alias = "trusted-host", - env = "UV_INSECURE_HOST", + env = EnvVars::UV_INSECURE_HOST, value_delimiter = ' ', value_parser = parse_insecure_host, help_heading = "Index options" @@ -4297,7 +4449,7 @@ pub struct ResolverInstallerArgs { #[arg( long, value_enum, - env = "UV_RESOLUTION", + env = EnvVars::UV_RESOLUTION, help_heading = "Resolver options" )] pub resolution: Option, @@ -4310,7 +4462,7 @@ pub struct ResolverInstallerArgs { #[arg( long, value_enum, - env = "UV_PRERELEASE", + env = EnvVars::UV_PRERELEASE, help_heading = "Resolver options" )] pub prerelease: Option, @@ -4334,7 +4486,7 @@ pub struct ResolverInstallerArgs { long, overrides_with("build_isolation"), help_heading = "Build options", - env = "UV_NO_BUILD_ISOLATION", + env = EnvVars::UV_NO_BUILD_ISOLATION, value_parser = clap::builder::BoolishValueParser::new(), )] pub no_build_isolation: bool, @@ -4357,7 +4509,7 @@ pub struct ResolverInstallerArgs { /// /// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same /// format (e.g., `2006-12-02`) in your system's configured time zone. - #[arg(long, env = "UV_EXCLUDE_NEWER", help_heading = "Resolver options")] + #[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")] pub exclude_newer: Option, /// The method to use when installing packages from the global cache. @@ -4367,7 +4519,7 @@ pub struct ResolverInstallerArgs { #[arg( long, value_enum, - env = "UV_LINK_MODE", + env = EnvVars::UV_LINK_MODE, help_heading = "Installer options" )] pub link_mode: Option, @@ -4387,7 +4539,7 @@ pub struct ResolverInstallerArgs { alias = "compile", overrides_with("no_compile_bytecode"), help_heading = "Installer options", - env = "UV_COMPILE_BYTECODE", + env = EnvVars::UV_COMPILE_BYTECODE, value_parser = clap::builder::BoolishValueParser::new(), )] pub compile_bytecode: bool, @@ -4430,7 +4582,8 @@ pub struct DisplayTreeArgs { #[arg(long)] pub no_dedupe: bool, - /// Show the reverse dependencies for the given package. This flag will invert the tree and display the packages that depend on the given package. + /// Show the reverse dependencies for the given package. This flag will invert the tree and + /// display the packages that depend on the given package. #[arg(long, alias = "reverse")] pub invert: bool, } @@ -4444,22 +4597,23 @@ pub struct PublishArgs { #[arg(default_value = "dist/*")] pub files: Vec, - /// The URL of the upload endpoint. + /// The URL of the upload endpoint (not the index URL). /// - /// Note that this typically differs from the index URL. + /// Note that there are typically different URLs for index access (e.g., `https:://.../simple`) + /// and index upload. /// /// Defaults to PyPI's publish URL (). /// /// The default value is publish URL for PyPI (). - #[arg(long, env = "UV_PUBLISH_URL")] + #[arg(long, env = EnvVars::UV_PUBLISH_URL)] pub publish_url: Option, /// The username for the upload. - #[arg(short, long, env = "UV_PUBLISH_USERNAME")] + #[arg(short, long, env = EnvVars::UV_PUBLISH_USERNAME)] pub username: Option, /// The password for the upload. - #[arg(short, long, env = "UV_PUBLISH_PASSWORD")] + #[arg(short, long, env = EnvVars::UV_PUBLISH_PASSWORD)] pub password: Option, /// The token for the upload. @@ -4469,7 +4623,7 @@ pub struct PublishArgs { #[arg( short, long, - env = "UV_PUBLISH_TOKEN", + env = EnvVars::UV_PUBLISH_TOKEN, conflicts_with = "username", conflicts_with = "password" )] @@ -4489,7 +4643,7 @@ pub struct PublishArgs { /// use the `keyring` CLI to handle authentication. /// /// Defaults to `disabled`. - #[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")] + #[arg(long, value_enum, env = EnvVars::UV_KEYRING_PROVIDER)] pub keyring_provider: Option, /// Allow insecure connections to a host. @@ -4505,7 +4659,7 @@ pub struct PublishArgs { #[arg( long, alias = "trusted-host", - env = "UV_INSECURE_HOST", + env = EnvVars::UV_INSECURE_HOST, value_delimiter = ' ', value_parser = parse_insecure_host, )] diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index 3fe5d0e77..404129793 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -1,7 +1,8 @@ use uv_cache::Refresh; use uv_configuration::ConfigSettings; +use uv_distribution_types::{PipExtraIndex, PipFindLinks, PipIndex}; use uv_resolver::PrereleaseMode; -use uv_settings::{PipOptions, ResolverInstallerOptions, ResolverOptions}; +use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions}; use crate::{ BuildOptionsArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs, @@ -186,6 +187,8 @@ impl From for PipOptions { impl From for PipOptions { fn from(args: IndexArgs) -> Self { let IndexArgs { + default_index, + index, index_url, extra_index_url, no_index, @@ -193,11 +196,18 @@ impl From for PipOptions { } = args; Self { - index_url: index_url.and_then(Maybe::into_option), - extra_index_url: extra_index_url.map(|extra_index_url| { - extra_index_url + index: default_index + .and_then(Maybe::into_option) + .map(|default_index| vec![default_index]) + .combine( + index.map(|index| index.into_iter().filter_map(Maybe::into_option).collect()), + ), + index_url: index_url.and_then(Maybe::into_option).map(PipIndex::from), + extra_index_url: extra_index_url.map(|extra_index_urls| { + extra_index_urls .into_iter() .filter_map(Maybe::into_option) + .map(PipExtraIndex::from) .collect() }), no_index: if no_index { Some(true) } else { None }, @@ -205,6 +215,7 @@ impl From for PipOptions { find_links .into_iter() .filter_map(Maybe::into_option) + .map(PipFindLinks::from) .collect() }), ..PipOptions::default() @@ -247,6 +258,15 @@ pub fn resolver_options( } = build_args; ResolverOptions { + index: index_args + .default_index + .and_then(Maybe::into_option) + .map(|default_index| vec![default_index]) + .combine( + index_args + .index + .map(|index| index.into_iter().filter_map(Maybe::into_option).collect()), + ), index_url: index_args.index_url.and_then(Maybe::into_option), extra_index_url: index_args.extra_index_url.map(|extra_index_url| { extra_index_url @@ -335,7 +355,16 @@ pub fn resolver_installer_options( no_binary_package, } = build_args; + let default_index = index_args + .default_index + .and_then(Maybe::into_option) + .map(|default_index| vec![default_index]); + let index = index_args + .index + .map(|index| index.into_iter().filter_map(Maybe::into_option).collect()); + ResolverInstallerOptions { + index: default_index.combine(index), index_url: index_args.index_url.and_then(Maybe::into_option), extra_index_url: index_args.extra_index_url.map(|extra_index_url| { extra_index_url diff --git a/crates/uv-cli/src/version.rs b/crates/uv-cli/src/version.rs index 87593f787..9eaa462b2 100644 --- a/crates/uv-cli/src/version.rs +++ b/crates/uv-cli/src/version.rs @@ -77,73 +77,4 @@ pub fn version() -> VersionInfo { } #[cfg(test)] -mod tests { - use insta::{assert_json_snapshot, assert_snapshot}; - - use super::{CommitInfo, VersionInfo}; - - #[test] - fn version_formatting() { - let version = VersionInfo { - version: "0.0.0".to_string(), - commit_info: None, - }; - assert_snapshot!(version, @"0.0.0"); - } - - #[test] - fn version_formatting_with_commit_info() { - let version = VersionInfo { - version: "0.0.0".to_string(), - commit_info: Some(CommitInfo { - short_commit_hash: "53b0f5d92".to_string(), - commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(), - last_tag: Some("v0.0.1".to_string()), - commit_date: "2023-10-19".to_string(), - commits_since_last_tag: 0, - }), - }; - assert_snapshot!(version, @"0.0.0 (53b0f5d92 2023-10-19)"); - } - - #[test] - fn version_formatting_with_commits_since_last_tag() { - let version = VersionInfo { - version: "0.0.0".to_string(), - commit_info: Some(CommitInfo { - short_commit_hash: "53b0f5d92".to_string(), - commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(), - last_tag: Some("v0.0.1".to_string()), - commit_date: "2023-10-19".to_string(), - commits_since_last_tag: 24, - }), - }; - assert_snapshot!(version, @"0.0.0+24 (53b0f5d92 2023-10-19)"); - } - - #[test] - fn version_serializable() { - let version = VersionInfo { - version: "0.0.0".to_string(), - commit_info: Some(CommitInfo { - short_commit_hash: "53b0f5d92".to_string(), - commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(), - last_tag: Some("v0.0.1".to_string()), - commit_date: "2023-10-19".to_string(), - commits_since_last_tag: 0, - }), - }; - assert_json_snapshot!(version, @r###" - { - "version": "0.0.0", - "commit_info": { - "short_commit_hash": "53b0f5d92", - "commit_hash": "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7", - "commit_date": "2023-10-19", - "last_tag": "v0.0.1", - "commits_since_last_tag": 0 - } - } - "###); - } -} +mod tests; diff --git a/crates/uv-cli/src/version/tests.rs b/crates/uv-cli/src/version/tests.rs new file mode 100644 index 000000000..de54bd6e1 --- /dev/null +++ b/crates/uv-cli/src/version/tests.rs @@ -0,0 +1,68 @@ +use insta::{assert_json_snapshot, assert_snapshot}; + +use super::{CommitInfo, VersionInfo}; + +#[test] +fn version_formatting() { + let version = VersionInfo { + version: "0.0.0".to_string(), + commit_info: None, + }; + assert_snapshot!(version, @"0.0.0"); +} + +#[test] +fn version_formatting_with_commit_info() { + let version = VersionInfo { + version: "0.0.0".to_string(), + commit_info: Some(CommitInfo { + short_commit_hash: "53b0f5d92".to_string(), + commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(), + last_tag: Some("v0.0.1".to_string()), + commit_date: "2023-10-19".to_string(), + commits_since_last_tag: 0, + }), + }; + assert_snapshot!(version, @"0.0.0 (53b0f5d92 2023-10-19)"); +} + +#[test] +fn version_formatting_with_commits_since_last_tag() { + let version = VersionInfo { + version: "0.0.0".to_string(), + commit_info: Some(CommitInfo { + short_commit_hash: "53b0f5d92".to_string(), + commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(), + last_tag: Some("v0.0.1".to_string()), + commit_date: "2023-10-19".to_string(), + commits_since_last_tag: 24, + }), + }; + assert_snapshot!(version, @"0.0.0+24 (53b0f5d92 2023-10-19)"); +} + +#[test] +fn version_serializable() { + let version = VersionInfo { + version: "0.0.0".to_string(), + commit_info: Some(CommitInfo { + short_commit_hash: "53b0f5d92".to_string(), + commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(), + last_tag: Some("v0.0.1".to_string()), + commit_date: "2023-10-19".to_string(), + commits_since_last_tag: 0, + }), + }; + assert_json_snapshot!(version, @r#" + { + "version": "0.0.0", + "commit_info": { + "short_commit_hash": "53b0f5d92", + "commit_hash": "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7", + "commit_date": "2023-10-19", + "last_tag": "v0.0.1", + "commits_since_last_tag": 0 + } + } + "#); +} diff --git a/crates/uv-client/Cargo.toml b/crates/uv-client/Cargo.toml index 2147d604b..7bea5ab97 100644 --- a/crates/uv-client/Cargo.toml +++ b/crates/uv-client/Cargo.toml @@ -3,6 +3,9 @@ name = "uv-client" version = "0.0.1" edition = "2021" +[lib] +doctest = false + [lints] workspace = true @@ -20,6 +23,7 @@ uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } +uv-static = { workspace = true } uv-version = { workspace = true } uv-warnings = { workspace = true } diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index ad6f55b75..0cc0f403a 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -17,6 +17,7 @@ use uv_configuration::{KeyringProviderType, TrustedHost}; use uv_fs::Simplified; use uv_pep508::MarkerEnvironment; use uv_platform_tags::Platform; +use uv_static::EnvVars; use uv_version::version; use uv_warnings::warn_user_once; @@ -156,7 +157,7 @@ impl<'a> BaseClientBuilder<'a> { } // Check for the presence of an `SSL_CERT_FILE`. - let ssl_cert_file_exists = env::var_os("SSL_CERT_FILE").is_some_and(|path| { + let ssl_cert_file_exists = env::var_os(EnvVars::SSL_CERT_FILE).is_some_and(|path| { let path_exists = Path::new(&path).exists(); if !path_exists { warn_user_once!( @@ -169,9 +170,9 @@ impl<'a> BaseClientBuilder<'a> { // Timeout options, matching https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout // `UV_REQUEST_TIMEOUT` is provided for backwards compatibility with v0.1.6 - let timeout = env::var("UV_HTTP_TIMEOUT") - .or_else(|_| env::var("UV_REQUEST_TIMEOUT")) - .or_else(|_| env::var("HTTP_TIMEOUT")) + let timeout = env::var(EnvVars::UV_HTTP_TIMEOUT) + .or_else(|_| env::var(EnvVars::UV_REQUEST_TIMEOUT)) + .or_else(|_| env::var(EnvVars::HTTP_TIMEOUT)) .and_then(|value| { value.parse::() .map(Duration::from_secs) @@ -260,7 +261,7 @@ impl<'a> BaseClientBuilder<'a> { }; // Configure mTLS. - let client_builder = if let Some(ssl_client_cert) = env::var_os("SSL_CLIENT_CERT") { + let client_builder = if let Some(ssl_client_cert) = env::var_os(EnvVars::SSL_CLIENT_CERT) { match read_identity(&ssl_client_cert) { Ok(identity) => client_builder.identity(identity), Err(err) => { diff --git a/crates/uv-client/src/error.rs b/crates/uv-client/src/error.rs index 730223534..cded0db49 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -56,6 +56,7 @@ impl Error { match &*self.kind { // The server doesn't support range requests (as reported by the `HEAD` check). ErrorKind::AsyncHttpRangeReader( + _, AsyncHttpRangeReaderError::HttpRangeRequestUnsupported, ) => { return true; @@ -63,6 +64,7 @@ impl Error { // The server doesn't support range requests (it doesn't return the necessary headers). ErrorKind::AsyncHttpRangeReader( + _, AsyncHttpRangeReaderError::ContentLengthMissing | AsyncHttpRangeReaderError::ContentRangeMissing, ) => { @@ -187,8 +189,8 @@ pub enum ErrorKind { #[error("Received some unexpected HTML from {url}")] BadHtml { source: html::Error, url: Url }, - #[error(transparent)] - AsyncHttpRangeReader(#[from] AsyncHttpRangeReaderError), + #[error("Failed to read zip with range requests: `{0}`")] + AsyncHttpRangeReader(Url, #[source] AsyncHttpRangeReaderError), #[error("{0} is not a valid wheel filename")] WheelFilename(#[source] WheelFilenameError), diff --git a/crates/uv-client/src/flat_index.rs b/crates/uv-client/src/flat_index.rs index 23d72d2f0..9b9d6fe61 100644 --- a/crates/uv-client/src/flat_index.rs +++ b/crates/uv-client/src/flat_index.rs @@ -5,13 +5,14 @@ use reqwest::Response; use tracing::{debug, info_span, warn, Instrument}; use url::Url; -use crate::cached_client::{CacheControl, CachedClientError}; -use crate::html::SimpleHtml; -use crate::{Connectivity, Error, ErrorKind, OwnedArchive, RegistryClient}; use uv_cache::{Cache, CacheBucket}; use uv_cache_key::cache_digest; use uv_distribution_filename::DistFilename; -use uv_distribution_types::{File, FileLocation, FlatIndexLocation, IndexUrl, UrlString}; +use uv_distribution_types::{File, FileLocation, IndexUrl, UrlString}; + +use crate::cached_client::{CacheControl, CachedClientError}; +use crate::html::SimpleHtml; +use crate::{Connectivity, Error, ErrorKind, OwnedArchive, RegistryClient}; #[derive(Debug, thiserror::Error)] pub enum FlatIndexError { @@ -94,19 +95,19 @@ impl<'a> FlatIndexClient<'a> { #[allow(clippy::result_large_err)] pub async fn fetch( &self, - indexes: impl Iterator, + indexes: impl Iterator, ) -> Result { let mut fetches = futures::stream::iter(indexes) .map(|index| async move { let entries = match index { - FlatIndexLocation::Path(url) => { + IndexUrl::Path(url) => { let path = url .to_file_path() .map_err(|()| FlatIndexError::NonFileUrl(url.to_url()))?; Self::read_from_directory(&path, index) .map_err(|err| FlatIndexError::FindLinksDirectory(path.clone(), err))? } - FlatIndexLocation::Url(url) => self + IndexUrl::Pypi(url) | IndexUrl::Url(url) => self .read_from_url(url, index) .await .map_err(|err| FlatIndexError::FindLinksUrl(url.to_url(), err))?, @@ -136,7 +137,7 @@ impl<'a> FlatIndexClient<'a> { async fn read_from_url( &self, url: &Url, - flat_index: &FlatIndexLocation, + flat_index: &IndexUrl, ) -> Result { let cache_entry = self.cache.entry( CacheBucket::FlatIndex, @@ -210,7 +211,7 @@ impl<'a> FlatIndexClient<'a> { Some(( DistFilename::try_from_normalized_filename(&file.filename)?, file, - IndexUrl::from(flat_index.clone()), + flat_index.clone(), )) }) .collect(); @@ -226,7 +227,7 @@ impl<'a> FlatIndexClient<'a> { /// Read a flat remote index from a `--find-links` directory. fn read_from_directory( path: &Path, - flat_index: &FlatIndexLocation, + flat_index: &IndexUrl, ) -> Result { let mut dists = Vec::new(); for entry in fs_err::read_dir(path)? { @@ -279,7 +280,7 @@ impl<'a> FlatIndexClient<'a> { ); continue; }; - dists.push((filename, file, IndexUrl::from(flat_index.clone()))); + dists.push((filename, file, flat_index.clone())); } Ok(FlatIndexEntries::from_entries(dists)) } diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs index 8eb2f5c84..838bf0f17 100644 --- a/crates/uv-client/src/html.rs +++ b/crates/uv-client/src/html.rs @@ -207,1000 +207,4 @@ pub enum Error { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_sha256() { - 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); - } - - #[test] - fn parse_md5() { - 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha256: None, - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#md5=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); - } - - #[test] - fn parse_base() { - 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( - "index.python.org", - ), - ), - port: None, - path: "/", - query: None, - fragment: None, - }, - ), - files: [ - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); - } - - #[test] - fn parse_escaped_fragment() { - let text = r#" - - - -

Links for jinja2

- Jinja2-3.1.2+233fca715f49-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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2+233fca715f49-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2+233fca715f49-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); - } - - #[test] - fn parse_encoded_fragment() { - 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "4095ada29e51070f7d199a0a5bdf5c8d8e238e03f0bf4dcc02571e78c9ae800d", - ), - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256%3D4095ada29e51070f7d199a0a5bdf5c8d8e238e03f0bf4dcc02571e78c9ae800d", - yanked: None, - }, - ], - } - "###); - } - - #[test] - fn parse_quoted_filepath() { - let text = r#" - - - -

Links for jinja2

- cpu/torchtext-0.17.0%2Bcpu-cp39-cp39-win_amd64.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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "torchtext-0.17.0+cpu-cp39-cp39-win_amd64.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "cpu/torchtext-0.17.0%2Bcpu-cp39-cp39-win_amd64.whl", - yanked: None, - }, - ], - } - "###); - } - - #[test] - fn parse_missing_hash() { - 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 { - core_metadata: None, - dist_info_metadata: None, - data_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", - yanked: None, - }, - ], - } - "###); - } - - #[test] - fn parse_missing_href() { - 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_err(); - insta::assert_snapshot!(result, @"Missing href attribute on anchor link"); - } - - #[test] - fn parse_empty_href() { - 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_err(); - insta::assert_snapshot!(result, @"Missing href attribute on anchor link"); - } - - #[test] - fn parse_empty_fragment() { - 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 { - core_metadata: None, - dist_info_metadata: None, - data_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#", - yanked: None, - }, - ], - } - "###); - } - - #[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 { - core_metadata: None, - dist_info_metadata: None, - data_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#" - - - -

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_err(); - insta::assert_snapshot!(result, @"Unexpected fragment (expected `#sha256=...` or similar) on URL: sha256"); - } - - #[test] - fn parse_unknown_hash() { - 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_err(); - insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, or `sha512`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`"); - } - - #[test] - fn parse_flat_index_html() { - let text = r#" - - - - - cuda100/jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl
- cuda100/jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl
- - - "#; - let base = Url::parse("https://storage.googleapis.com/jax-releases/jax_cuda_releases.html") - .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( - "storage.googleapis.com", - ), - ), - port: None, - path: "/jax-releases/jax_cuda_releases.html", - query: None, - fragment: None, - }, - ), - files: [ - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl", - yanked: None, - }, - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl", - yanked: None, - }, - ], - } - "###); - } - - /// Test for AWS Code Artifact - /// - /// See: - #[test] - fn parse_code_artifact_index_html() { - let text = r#" - - - - Links for flask - - -

Links for flask

- Flask-0.1.tar.gz -
- Flask-0.10.1.tar.gz -
- flask-3.0.1.tar.gz -
- - - "#; - let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/") - .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( - "account.d.codeartifact.us-west-2.amazonaws.com", - ), - ), - port: None, - path: "/pypi/shared-packages-pypi/simple/flask/", - query: None, - fragment: None, - }, - ), - files: [ - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Flask-0.1.tar.gz", - hashes: Hashes { - md5: None, - sha256: Some( - "9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", - ), - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "0.1/Flask-0.1.tar.gz#sha256=9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", - yanked: None, - }, - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Flask-0.10.1.tar.gz", - hashes: Hashes { - md5: None, - sha256: Some( - "4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", - ), - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "0.10.1/Flask-0.10.1.tar.gz#sha256=4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", - yanked: None, - }, - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "flask-3.0.1.tar.gz", - hashes: Hashes { - md5: None, - sha256: Some( - "6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", - ), - sha384: None, - sha512: None, - }, - requires_python: Some( - Ok( - VersionSpecifiers( - [ - VersionSpecifier { - operator: GreaterThanEqual, - version: "3.8", - }, - ], - ), - ), - ), - size: None, - upload_time: None, - url: "3.0.1/flask-3.0.1.tar.gz#sha256=6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", - yanked: None, - }, - ], - } - "###); - } - - #[test] - fn parse_file_requires_python_trailing_comma() { - 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha384: None, - sha512: None, - }, - requires_python: Some( - Ok( - VersionSpecifiers( - [ - VersionSpecifier { - operator: GreaterThanEqual, - version: "3.8", - }, - ], - ), - ), - ), - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); - } - - /// Respect PEP 714 (see: ). - #[test] - fn parse_core_metadata() { - let text = r#" - - - -

Links for jinja2

- Jinja2-3.1.2-py3-none-any.whl
- Jinja2-3.1.3-py3-none-any.whl
- Jinja2-3.1.4-py3-none-any.whl
- Jinja2-3.1.5-py3-none-any.whl
- Jinja2-3.1.6-py3-none-any.whl
- - - "#; - let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/") - .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( - "account.d.codeartifact.us-west-2.amazonaws.com", - ), - ), - port: None, - path: "/pypi/shared-packages-pypi/simple/flask/", - query: None, - fragment: None, - }, - ), - files: [ - File { - core_metadata: Some( - Bool( - true, - ), - ), - dist_info_metadata: None, - data_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", - yanked: None, - }, - File { - core_metadata: Some( - Bool( - true, - ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.3-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.3-py3-none-any.whl", - yanked: None, - }, - File { - core_metadata: Some( - Bool( - false, - ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.4-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.4-py3-none-any.whl", - yanked: None, - }, - File { - core_metadata: Some( - Bool( - false, - ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.5-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.5-py3-none-any.whl", - yanked: None, - }, - File { - core_metadata: Some( - Bool( - true, - ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.6-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.6-py3-none-any.whl", - yanked: None, - }, - ], - } - "###); - } -} +mod tests; diff --git a/crates/uv-client/src/html/tests.rs b/crates/uv-client/src/html/tests.rs new file mode 100644 index 000000000..c8ba90390 --- /dev/null +++ b/crates/uv-client/src/html/tests.rs @@ -0,0 +1,995 @@ +use super::*; + +#[test] +fn parse_sha256() { + 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 { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); +} + +#[test] +fn parse_md5() { + 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 { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#md5=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); +} + +#[test] +fn parse_base() { + 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( + "index.python.org", + ), + ), + port: None, + path: "/", + query: None, + fragment: None, + }, + ), + files: [ + File { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); +} + +#[test] +fn parse_escaped_fragment() { + let text = r#" + + + +

Links for jinja2

+Jinja2-3.1.2+233fca715f49-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 { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.2+233fca715f49-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2+233fca715f49-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); +} + +#[test] +fn parse_encoded_fragment() { + 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 { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "4095ada29e51070f7d199a0a5bdf5c8d8e238e03f0bf4dcc02571e78c9ae800d", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256%3D4095ada29e51070f7d199a0a5bdf5c8d8e238e03f0bf4dcc02571e78c9ae800d", + yanked: None, + }, + ], + } + "###); +} + +#[test] +fn parse_quoted_filepath() { + let text = r#" + + + +

Links for jinja2

+cpu/torchtext-0.17.0%2Bcpu-cp39-cp39-win_amd64.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 { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "torchtext-0.17.0+cpu-cp39-cp39-win_amd64.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "cpu/torchtext-0.17.0%2Bcpu-cp39-cp39-win_amd64.whl", + yanked: None, + }, + ], + } + "###); +} + +#[test] +fn parse_missing_hash() { + 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 { + core_metadata: None, + dist_info_metadata: None, + data_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", + yanked: None, + }, + ], + } + "###); +} + +#[test] +fn parse_missing_href() { + 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_err(); + insta::assert_snapshot!(result, @"Missing href attribute on anchor link"); +} + +#[test] +fn parse_empty_href() { + 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_err(); + insta::assert_snapshot!(result, @"Missing href attribute on anchor link"); +} + +#[test] +fn parse_empty_fragment() { + 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 { + core_metadata: None, + dist_info_metadata: None, + data_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#", + yanked: None, + }, + ], + } + "###); +} + +#[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 { + core_metadata: None, + dist_info_metadata: None, + data_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#" + + + +

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_err(); + insta::assert_snapshot!(result, @"Unexpected fragment (expected `#sha256=...` or similar) on URL: sha256"); +} + +#[test] +fn parse_unknown_hash() { + 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_err(); + insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, or `sha512`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`"); +} + +#[test] +fn parse_flat_index_html() { + let text = r#" + + + + + cuda100/jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl
+ cuda100/jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl
+ + + "#; + let base = + Url::parse("https://storage.googleapis.com/jax-releases/jax_cuda_releases.html").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( + "storage.googleapis.com", + ), + ), + port: None, + path: "/jax-releases/jax_cuda_releases.html", + query: None, + fragment: None, + }, + ), + files: [ + File { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl", + yanked: None, + }, + File { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl", + yanked: None, + }, + ], + } + "###); +} + +/// Test for AWS Code Artifact +/// +/// See: +#[test] +fn parse_code_artifact_index_html() { + let text = r#" + + + + Links for flask + + +

Links for flask

+ Flask-0.1.tar.gz +
+ Flask-0.10.1.tar.gz +
+ flask-3.0.1.tar.gz +
+ + + "#; + let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/") + .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( + "account.d.codeartifact.us-west-2.amazonaws.com", + ), + ), + port: None, + path: "/pypi/shared-packages-pypi/simple/flask/", + query: None, + fragment: None, + }, + ), + files: [ + File { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Flask-0.1.tar.gz", + hashes: Hashes { + md5: None, + sha256: Some( + "9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "0.1/Flask-0.1.tar.gz#sha256=9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", + yanked: None, + }, + File { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Flask-0.10.1.tar.gz", + hashes: Hashes { + md5: None, + sha256: Some( + "4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "0.10.1/Flask-0.10.1.tar.gz#sha256=4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", + yanked: None, + }, + File { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "flask-3.0.1.tar.gz", + hashes: Hashes { + md5: None, + sha256: Some( + "6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", + ), + sha384: None, + sha512: None, + }, + requires_python: Some( + Ok( + VersionSpecifiers( + [ + VersionSpecifier { + operator: GreaterThanEqual, + version: "3.8", + }, + ], + ), + ), + ), + size: None, + upload_time: None, + url: "3.0.1/flask-3.0.1.tar.gz#sha256=6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", + yanked: None, + }, + ], + } + "###); +} + +#[test] +fn parse_file_requires_python_trailing_comma() { + 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 { + core_metadata: None, + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + }, + requires_python: Some( + Ok( + VersionSpecifiers( + [ + VersionSpecifier { + operator: GreaterThanEqual, + version: "3.8", + }, + ], + ), + ), + ), + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); +} + +/// Respect PEP 714 (see: ). +#[test] +fn parse_core_metadata() { + let text = r#" + + + +

Links for jinja2

+Jinja2-3.1.2-py3-none-any.whl
+Jinja2-3.1.3-py3-none-any.whl
+Jinja2-3.1.4-py3-none-any.whl
+Jinja2-3.1.5-py3-none-any.whl
+Jinja2-3.1.6-py3-none-any.whl
+ + + "#; + let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/") + .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( + "account.d.codeartifact.us-west-2.amazonaws.com", + ), + ), + port: None, + path: "/pypi/shared-packages-pypi/simple/flask/", + query: None, + fragment: None, + }, + ), + files: [ + File { + core_metadata: Some( + Bool( + true, + ), + ), + dist_info_metadata: None, + data_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", + yanked: None, + }, + File { + core_metadata: Some( + Bool( + true, + ), + ), + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.3-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.3-py3-none-any.whl", + yanked: None, + }, + File { + core_metadata: Some( + Bool( + false, + ), + ), + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.4-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.4-py3-none-any.whl", + yanked: None, + }, + File { + core_metadata: Some( + Bool( + false, + ), + ), + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.5-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.5-py3-none-any.whl", + yanked: None, + }, + File { + core_metadata: Some( + Bool( + true, + ), + ), + dist_info_metadata: None, + data_dist_info_metadata: None, + filename: "Jinja2-3.1.6-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.6-py3-none-any.whl", + yanked: None, + }, + ], + } + "###); +} diff --git a/crates/uv-client/src/httpcache/control.rs b/crates/uv-client/src/httpcache/control.rs index 6860386bf..e728d6f08 100644 --- a/crates/uv-client/src/httpcache/control.rs +++ b/crates/uv-client/src/httpcache/control.rs @@ -453,326 +453,4 @@ impl CacheControlDirective { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn cache_control_token() { - let cc: CacheControl = CacheControlParser::new(["no-cache"]).collect(); - assert!(cc.no_cache); - assert!(!cc.must_revalidate); - } - - #[test] - fn cache_control_max_age() { - let cc: CacheControl = CacheControlParser::new(["max-age=60"]).collect(); - assert_eq!(Some(60), cc.max_age_seconds); - assert!(!cc.must_revalidate); - } - - // [RFC 9111 S5.2.1.1] says that client MUST NOT quote max-age, but we - // support parsing it that way anyway. - // - // [RFC 9111 S5.2.1.1]: https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1 - #[test] - fn cache_control_max_age_quoted() { - let cc: CacheControl = CacheControlParser::new([r#"max-age="60""#]).collect(); - assert_eq!(Some(60), cc.max_age_seconds); - assert!(!cc.must_revalidate); - } - - #[test] - fn cache_control_max_age_invalid() { - let cc: CacheControl = CacheControlParser::new(["max-age=6a0"]).collect(); - assert_eq!(None, cc.max_age_seconds); - assert!(cc.must_revalidate); - } - - #[test] - fn cache_control_immutable() { - let cc: CacheControl = CacheControlParser::new(["max-age=31536000, immutable"]).collect(); - assert_eq!(Some(31_536_000), cc.max_age_seconds); - assert!(cc.immutable); - assert!(!cc.must_revalidate); - } - - #[test] - fn cache_control_unrecognized() { - let cc: CacheControl = CacheControlParser::new(["lion,max-age=60,zebra"]).collect(); - assert_eq!(Some(60), cc.max_age_seconds); - } - - #[test] - fn cache_control_invalid_squashes_remainder() { - let cc: CacheControl = CacheControlParser::new(["no-cache,\x00,max-age=60"]).collect(); - // The invalid data doesn't impact things before it. - assert!(cc.no_cache); - // The invalid data precludes parsing anything after. - assert_eq!(None, cc.max_age_seconds); - // The invalid contents should force revalidation. - assert!(cc.must_revalidate); - } - - #[test] - fn cache_control_invalid_squashes_remainder_but_not_other_header_values() { - let cc: CacheControl = - CacheControlParser::new(["no-cache,\x00,max-age=60", "max-stale=30"]).collect(); - // The invalid data doesn't impact things before it. - assert!(cc.no_cache); - // The invalid data precludes parsing anything after - // in the same header value, but not in other - // header values. - assert_eq!(Some(30), cc.max_stale_seconds); - // The invalid contents should force revalidation. - assert!(cc.must_revalidate); - } - - #[test] - fn cache_control_parse_token() { - let directives = CacheControlParser::new(["no-cache"]).collect::>(); - assert_eq!( - directives, - vec![CacheControlDirective { - name: "no-cache".to_string(), - value: vec![] - }] - ); - } - - #[test] - fn cache_control_parse_token_to_token_value() { - let directives = CacheControlParser::new(["max-age=60"]).collect::>(); - assert_eq!( - directives, - vec![CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - }] - ); - } - - #[test] - fn cache_control_parse_token_to_quoted_string() { - let directives = - CacheControlParser::new([r#"private="cookie,x-something-else""#]).collect::>(); - assert_eq!( - directives, - vec![CacheControlDirective { - name: "private".to_string(), - value: b"cookie,x-something-else".to_vec(), - }] - ); - } - - #[test] - fn cache_control_parse_token_to_quoted_string_with_escape() { - let directives = - CacheControlParser::new([r#"private="something\"crazy""#]).collect::>(); - assert_eq!( - directives, - vec![CacheControlDirective { - name: "private".to_string(), - value: br#"something"crazy"#.to_vec(), - }] - ); - } - - #[test] - fn cache_control_parse_multiple_directives() { - let header = r#"max-age=60, no-cache, private="cookie", no-transform"#; - let directives = CacheControlParser::new([header]).collect::>(); - assert_eq!( - directives, - vec![ - CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - }, - CacheControlDirective { - name: "no-cache".to_string(), - value: vec![] - }, - CacheControlDirective { - name: "private".to_string(), - value: b"cookie".to_vec(), - }, - CacheControlDirective { - name: "no-transform".to_string(), - value: vec![] - }, - ] - ); - } - - #[test] - fn cache_control_parse_multiple_directives_across_multiple_header_values() { - let headers = [ - r"max-age=60, no-cache", - r#"private="cookie""#, - r"no-transform", - ]; - let directives = CacheControlParser::new(headers).collect::>(); - assert_eq!( - directives, - vec![ - CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - }, - CacheControlDirective { - name: "no-cache".to_string(), - value: vec![] - }, - CacheControlDirective { - name: "private".to_string(), - value: b"cookie".to_vec(), - }, - CacheControlDirective { - name: "no-transform".to_string(), - value: vec![] - }, - ] - ); - } - - #[test] - fn cache_control_parse_one_header_invalid() { - let headers = [ - r"max-age=60, no-cache", - r#", private="cookie""#, - r"no-transform", - ]; - let directives = CacheControlParser::new(headers).collect::>(); - assert_eq!( - directives, - vec![ - CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - }, - CacheControlDirective { - name: "no-cache".to_string(), - value: vec![] - }, - CacheControlDirective { - name: "must-revalidate".to_string(), - value: vec![] - }, - CacheControlDirective { - name: "no-transform".to_string(), - value: vec![] - }, - ] - ); - } - - #[test] - fn cache_control_parse_invalid_directive_drops_remainder() { - let header = r#"max-age=60, no-cache, ="cookie", no-transform"#; - let directives = CacheControlParser::new([header]).collect::>(); - assert_eq!( - directives, - vec![ - CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - }, - CacheControlDirective { - name: "no-cache".to_string(), - value: vec![] - }, - CacheControlDirective { - name: "must-revalidate".to_string(), - value: vec![] - }, - ] - ); - } - - #[test] - fn cache_control_parse_name_normalized() { - let header = r"MAX-AGE=60"; - let directives = CacheControlParser::new([header]).collect::>(); - assert_eq!( - directives, - vec![CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - },] - ); - } - - // When a duplicate directive is found, we keep the first one - // and add in a `must-revalidate` directive to indicate that - // things are stale and the client should do a re-check. - #[test] - fn cache_control_parse_duplicate_directives() { - let header = r"max-age=60, no-cache, max-age=30"; - let directives = CacheControlParser::new([header]).collect::>(); - assert_eq!( - directives, - vec![ - CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - }, - CacheControlDirective { - name: "no-cache".to_string(), - value: vec![] - }, - CacheControlDirective { - name: "must-revalidate".to_string(), - value: vec![] - }, - ] - ); - } - - #[test] - fn cache_control_parse_duplicate_directives_across_headers() { - let headers = [r"max-age=60, no-cache", r"max-age=30"]; - let directives = CacheControlParser::new(headers).collect::>(); - assert_eq!( - directives, - vec![ - CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - }, - CacheControlDirective { - name: "no-cache".to_string(), - value: vec![] - }, - CacheControlDirective { - name: "must-revalidate".to_string(), - value: vec![] - }, - ] - ); - } - - // Tests that we don't emit must-revalidate multiple times - // even when something is duplicated multiple times. - #[test] - fn cache_control_parse_duplicate_redux() { - let header = r"max-age=60, no-cache, no-cache, max-age=30"; - let directives = CacheControlParser::new([header]).collect::>(); - assert_eq!( - directives, - vec![ - CacheControlDirective { - name: "max-age".to_string(), - value: b"60".to_vec(), - }, - CacheControlDirective { - name: "no-cache".to_string(), - value: vec![] - }, - CacheControlDirective { - name: "must-revalidate".to_string(), - value: vec![] - }, - ] - ); - } -} +mod tests; diff --git a/crates/uv-client/src/httpcache/control/tests.rs b/crates/uv-client/src/httpcache/control/tests.rs new file mode 100644 index 000000000..34cf770fa --- /dev/null +++ b/crates/uv-client/src/httpcache/control/tests.rs @@ -0,0 +1,320 @@ +use super::*; + +#[test] +fn cache_control_token() { + let cc: CacheControl = CacheControlParser::new(["no-cache"]).collect(); + assert!(cc.no_cache); + assert!(!cc.must_revalidate); +} + +#[test] +fn cache_control_max_age() { + let cc: CacheControl = CacheControlParser::new(["max-age=60"]).collect(); + assert_eq!(Some(60), cc.max_age_seconds); + assert!(!cc.must_revalidate); +} + +// [RFC 9111 S5.2.1.1] says that client MUST NOT quote max-age, but we +// support parsing it that way anyway. +// +// [RFC 9111 S5.2.1.1]: https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1 +#[test] +fn cache_control_max_age_quoted() { + let cc: CacheControl = CacheControlParser::new([r#"max-age="60""#]).collect(); + assert_eq!(Some(60), cc.max_age_seconds); + assert!(!cc.must_revalidate); +} + +#[test] +fn cache_control_max_age_invalid() { + let cc: CacheControl = CacheControlParser::new(["max-age=6a0"]).collect(); + assert_eq!(None, cc.max_age_seconds); + assert!(cc.must_revalidate); +} + +#[test] +fn cache_control_immutable() { + let cc: CacheControl = CacheControlParser::new(["max-age=31536000, immutable"]).collect(); + assert_eq!(Some(31_536_000), cc.max_age_seconds); + assert!(cc.immutable); + assert!(!cc.must_revalidate); +} + +#[test] +fn cache_control_unrecognized() { + let cc: CacheControl = CacheControlParser::new(["lion,max-age=60,zebra"]).collect(); + assert_eq!(Some(60), cc.max_age_seconds); +} + +#[test] +fn cache_control_invalid_squashes_remainder() { + let cc: CacheControl = CacheControlParser::new(["no-cache,\x00,max-age=60"]).collect(); + // The invalid data doesn't impact things before it. + assert!(cc.no_cache); + // The invalid data precludes parsing anything after. + assert_eq!(None, cc.max_age_seconds); + // The invalid contents should force revalidation. + assert!(cc.must_revalidate); +} + +#[test] +fn cache_control_invalid_squashes_remainder_but_not_other_header_values() { + let cc: CacheControl = + CacheControlParser::new(["no-cache,\x00,max-age=60", "max-stale=30"]).collect(); + // The invalid data doesn't impact things before it. + assert!(cc.no_cache); + // The invalid data precludes parsing anything after + // in the same header value, but not in other + // header values. + assert_eq!(Some(30), cc.max_stale_seconds); + // The invalid contents should force revalidation. + assert!(cc.must_revalidate); +} + +#[test] +fn cache_control_parse_token() { + let directives = CacheControlParser::new(["no-cache"]).collect::>(); + assert_eq!( + directives, + vec![CacheControlDirective { + name: "no-cache".to_string(), + value: vec![] + }] + ); +} + +#[test] +fn cache_control_parse_token_to_token_value() { + let directives = CacheControlParser::new(["max-age=60"]).collect::>(); + assert_eq!( + directives, + vec![CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + }] + ); +} + +#[test] +fn cache_control_parse_token_to_quoted_string() { + let directives = + CacheControlParser::new([r#"private="cookie,x-something-else""#]).collect::>(); + assert_eq!( + directives, + vec![CacheControlDirective { + name: "private".to_string(), + value: b"cookie,x-something-else".to_vec(), + }] + ); +} + +#[test] +fn cache_control_parse_token_to_quoted_string_with_escape() { + let directives = CacheControlParser::new([r#"private="something\"crazy""#]).collect::>(); + assert_eq!( + directives, + vec![CacheControlDirective { + name: "private".to_string(), + value: br#"something"crazy"#.to_vec(), + }] + ); +} + +#[test] +fn cache_control_parse_multiple_directives() { + let header = r#"max-age=60, no-cache, private="cookie", no-transform"#; + let directives = CacheControlParser::new([header]).collect::>(); + assert_eq!( + directives, + vec![ + CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + }, + CacheControlDirective { + name: "no-cache".to_string(), + value: vec![] + }, + CacheControlDirective { + name: "private".to_string(), + value: b"cookie".to_vec(), + }, + CacheControlDirective { + name: "no-transform".to_string(), + value: vec![] + }, + ] + ); +} + +#[test] +fn cache_control_parse_multiple_directives_across_multiple_header_values() { + let headers = [ + r"max-age=60, no-cache", + r#"private="cookie""#, + r"no-transform", + ]; + let directives = CacheControlParser::new(headers).collect::>(); + assert_eq!( + directives, + vec![ + CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + }, + CacheControlDirective { + name: "no-cache".to_string(), + value: vec![] + }, + CacheControlDirective { + name: "private".to_string(), + value: b"cookie".to_vec(), + }, + CacheControlDirective { + name: "no-transform".to_string(), + value: vec![] + }, + ] + ); +} + +#[test] +fn cache_control_parse_one_header_invalid() { + let headers = [ + r"max-age=60, no-cache", + r#", private="cookie""#, + r"no-transform", + ]; + let directives = CacheControlParser::new(headers).collect::>(); + assert_eq!( + directives, + vec![ + CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + }, + CacheControlDirective { + name: "no-cache".to_string(), + value: vec![] + }, + CacheControlDirective { + name: "must-revalidate".to_string(), + value: vec![] + }, + CacheControlDirective { + name: "no-transform".to_string(), + value: vec![] + }, + ] + ); +} + +#[test] +fn cache_control_parse_invalid_directive_drops_remainder() { + let header = r#"max-age=60, no-cache, ="cookie", no-transform"#; + let directives = CacheControlParser::new([header]).collect::>(); + assert_eq!( + directives, + vec![ + CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + }, + CacheControlDirective { + name: "no-cache".to_string(), + value: vec![] + }, + CacheControlDirective { + name: "must-revalidate".to_string(), + value: vec![] + }, + ] + ); +} + +#[test] +fn cache_control_parse_name_normalized() { + let header = r"MAX-AGE=60"; + let directives = CacheControlParser::new([header]).collect::>(); + assert_eq!( + directives, + vec![CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + },] + ); +} + +// When a duplicate directive is found, we keep the first one +// and add in a `must-revalidate` directive to indicate that +// things are stale and the client should do a re-check. +#[test] +fn cache_control_parse_duplicate_directives() { + let header = r"max-age=60, no-cache, max-age=30"; + let directives = CacheControlParser::new([header]).collect::>(); + assert_eq!( + directives, + vec![ + CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + }, + CacheControlDirective { + name: "no-cache".to_string(), + value: vec![] + }, + CacheControlDirective { + name: "must-revalidate".to_string(), + value: vec![] + }, + ] + ); +} + +#[test] +fn cache_control_parse_duplicate_directives_across_headers() { + let headers = [r"max-age=60, no-cache", r"max-age=30"]; + let directives = CacheControlParser::new(headers).collect::>(); + assert_eq!( + directives, + vec![ + CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + }, + CacheControlDirective { + name: "no-cache".to_string(), + value: vec![] + }, + CacheControlDirective { + name: "must-revalidate".to_string(), + value: vec![] + }, + ] + ); +} + +// Tests that we don't emit must-revalidate multiple times +// even when something is duplicated multiple times. +#[test] +fn cache_control_parse_duplicate_redux() { + let header = r"max-age=60, no-cache, no-cache, max-age=30"; + let directives = CacheControlParser::new([header]).collect::>(); + assert_eq!( + directives, + vec![ + CacheControlDirective { + name: "max-age".to_string(), + value: b"60".to_vec(), + }, + CacheControlDirective { + name: "no-cache".to_string(), + value: vec![] + }, + CacheControlDirective { + name: "must-revalidate".to_string(), + value: vec![] + }, + ] + ); +} diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index f98ca7f8c..2930201ce 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -1,6 +1,7 @@ use async_http_range_reader::AsyncHttpRangeReader; use futures::{FutureExt, TryStreamExt}; use http::HeaderMap; +use itertools::Either; use reqwest::{Client, Response, StatusCode}; use reqwest_middleware::ClientWithMiddleware; use std::collections::BTreeMap; @@ -16,7 +17,7 @@ use uv_configuration::KeyringProviderType; use uv_configuration::{IndexStrategy, TrustedHost}; use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename}; use uv_distribution_types::{ - BuiltDist, File, FileLocation, IndexCapabilities, IndexUrl, IndexUrls, Name, + BuiltDist, File, FileLocation, Index, IndexCapabilities, IndexUrl, IndexUrls, Name, }; use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream}; use uv_normalize::PackageName; @@ -201,11 +202,19 @@ impl RegistryClient { /// and [PEP 691 – JSON-based Simple API for Python Package Indexes](https://peps.python.org/pep-0691/), /// which the pypi json api approximately implements. #[instrument("simple_api", skip_all, fields(package = % package_name))] - pub async fn simple( - &self, + pub async fn simple<'index>( + &'index self, package_name: &PackageName, - ) -> Result)>, Error> { - let mut it = self.index_urls.indexes().peekable(); + index: Option<&'index IndexUrl>, + capabilities: &IndexCapabilities, + ) -> Result)>, Error> { + let indexes = if let Some(index) = index { + Either::Left(std::iter::once(index)) + } else { + Either::Right(self.index_urls.indexes().map(Index::url)) + }; + + let mut it = indexes.peekable(); if it.peek().is_none() { return Err(ErrorKind::NoIndex(package_name.to_string()).into()); } @@ -214,7 +223,7 @@ impl RegistryClient { for index in it { match self.simple_single_index(package_name, index).await { Ok(metadata) => { - results.push((index.clone(), metadata)); + results.push((index, metadata)); // If we're only using the first match, we can stop here. if self.index_strategy == IndexStrategy::FirstIndex { @@ -222,22 +231,23 @@ impl RegistryClient { } } Err(err) => match err.into_kind() { - // The package is unavailable due to a lack of connectivity. - ErrorKind::Offline(_) => continue, - // The package could not be found in the remote index. - ErrorKind::WrappedReqwestError(err) => { - if err.status() == Some(StatusCode::NOT_FOUND) - || err.status() == Some(StatusCode::UNAUTHORIZED) - || err.status() == Some(StatusCode::FORBIDDEN) - { - continue; + ErrorKind::WrappedReqwestError(err) => match err.status() { + Some(StatusCode::NOT_FOUND) => {} + Some(StatusCode::UNAUTHORIZED) => { + capabilities.set_unauthorized(index.clone()); } - return Err(ErrorKind::from(err).into()); - } + Some(StatusCode::FORBIDDEN) => { + capabilities.set_forbidden(index.clone()); + } + _ => return Err(ErrorKind::from(err).into()), + }, + + // The package is unavailable due to a lack of connectivity. + ErrorKind::Offline(_) => {} // The package could not be found in the local index. - ErrorKind::FileNotFound(_) => continue, + ErrorKind::FileNotFound(_) => {} other => return Err(other.into()), }, @@ -625,7 +635,7 @@ impl RegistryClient { headers, ) .await - .map_err(ErrorKind::AsyncHttpRangeReader)?; + .map_err(|err| ErrorKind::AsyncHttpRangeReader(url.clone(), err))?; trace!("Getting metadata for {filename} by range request"); let text = wheel_metadata_from_remote_zip(filename, url, &mut reader).await?; let metadata = @@ -663,7 +673,7 @@ impl RegistryClient { // Mark the index as not supporting range requests. if let Some(index) = index { - capabilities.set_supports_range_requests(index.clone(), false); + capabilities.set_no_range_requests(index.clone()); } } else { return Err(err); @@ -901,107 +911,4 @@ impl Connectivity { } #[cfg(test)] -mod tests { - use std::str::FromStr; - - use url::Url; - - use uv_normalize::PackageName; - use uv_pypi_types::{JoinRelativeError, SimpleJson}; - - use crate::{html::SimpleHtml, SimpleMetadata, SimpleMetadatum}; - - #[test] - fn ignore_failing_files() { - // 1.7.7 has an invalid requires-python field (double comma), 1.7.8 is valid - let response = r#" - { - "files": [ - { - "core-metadata": false, - "data-dist-info-metadata": false, - "filename": "pyflyby-1.7.7.tar.gz", - "hashes": { - "sha256": "0c4d953f405a7be1300b440dbdbc6917011a07d8401345a97e72cd410d5fb291" - }, - "requires-python": ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*, !=3.2.*, !=3.3.*, !=3.4.*,, !=3.5.*, !=3.6.*, <4", - "size": 427200, - "upload-time": "2022-05-19T09:14:36.591835Z", - "url": "https://files.pythonhosted.org/packages/61/93/9fec62902d0b4fc2521333eba047bff4adbba41f1723a6382367f84ee522/pyflyby-1.7.7.tar.gz", - "yanked": false - }, - { - "core-metadata": false, - "data-dist-info-metadata": false, - "filename": "pyflyby-1.7.8.tar.gz", - "hashes": { - "sha256": "1ee37474f6da8f98653dbcc208793f50b7ace1d9066f49e2707750a5ba5d53c6" - }, - "requires-python": ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, <4", - "size": 424460, - "upload-time": "2022-08-04T10:42:02.190074Z", - "url": "https://files.pythonhosted.org/packages/ad/39/17180d9806a1c50197bc63b25d0f1266f745fc3b23f11439fccb3d6baa50/pyflyby-1.7.8.tar.gz", - "yanked": false - } - ] - } - "#; - let data: SimpleJson = serde_json::from_str(response).unwrap(); - let base = Url::parse("https://pypi.org/simple/pyflyby/").unwrap(); - let simple_metadata = SimpleMetadata::from_files( - data.files, - &PackageName::from_str("pyflyby").unwrap(), - &base, - ); - let versions: Vec = simple_metadata - .iter() - .map(|SimpleMetadatum { version, .. }| version.to_string()) - .collect(); - assert_eq!(versions, ["1.7.8".to_string()]); - } - - /// Test for AWS Code Artifact registry - /// - /// See: - #[test] - fn relative_urls_code_artifact() -> Result<(), JoinRelativeError> { - let text = r#" - - - - Links for flask - - -

Links for flask

- Flask-0.1.tar.gz -
- Flask-0.10.1.tar.gz -
- flask-3.0.1.tar.gz -
- - - "#; - - // Note the lack of a trailing `/` here is important for coverage of url-join behavior - let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask") - .unwrap(); - let SimpleHtml { base, files } = SimpleHtml::parse(text, &base).unwrap(); - - // Test parsing of the file urls - let urls = files - .iter() - .map(|file| uv_pypi_types::base_url_join_relative(base.as_url().as_str(), &file.url)) - .collect::, JoinRelativeError>>()?; - let urls = urls.iter().map(reqwest::Url::as_str).collect::>(); - insta::assert_debug_snapshot!(urls, @r###" - [ - "https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/0.1/Flask-0.1.tar.gz#sha256=9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", - "https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/0.10.1/Flask-0.10.1.tar.gz#sha256=4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", - "https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/3.0.1/flask-3.0.1.tar.gz#sha256=6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", - ] - "###); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-client/src/registry_client/tests.rs b/crates/uv-client/src/registry_client/tests.rs new file mode 100644 index 000000000..6e31a5cf3 --- /dev/null +++ b/crates/uv-client/src/registry_client/tests.rs @@ -0,0 +1,102 @@ +use std::str::FromStr; + +use url::Url; + +use uv_normalize::PackageName; +use uv_pypi_types::{JoinRelativeError, SimpleJson}; + +use crate::{html::SimpleHtml, SimpleMetadata, SimpleMetadatum}; + +#[test] +fn ignore_failing_files() { + // 1.7.7 has an invalid requires-python field (double comma), 1.7.8 is valid + let response = r#" + { + "files": [ + { + "core-metadata": false, + "data-dist-info-metadata": false, + "filename": "pyflyby-1.7.7.tar.gz", + "hashes": { + "sha256": "0c4d953f405a7be1300b440dbdbc6917011a07d8401345a97e72cd410d5fb291" + }, + "requires-python": ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*, !=3.2.*, !=3.3.*, !=3.4.*,, !=3.5.*, !=3.6.*, <4", + "size": 427200, + "upload-time": "2022-05-19T09:14:36.591835Z", + "url": "https://files.pythonhosted.org/packages/61/93/9fec62902d0b4fc2521333eba047bff4adbba41f1723a6382367f84ee522/pyflyby-1.7.7.tar.gz", + "yanked": false + }, + { + "core-metadata": false, + "data-dist-info-metadata": false, + "filename": "pyflyby-1.7.8.tar.gz", + "hashes": { + "sha256": "1ee37474f6da8f98653dbcc208793f50b7ace1d9066f49e2707750a5ba5d53c6" + }, + "requires-python": ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, <4", + "size": 424460, + "upload-time": "2022-08-04T10:42:02.190074Z", + "url": "https://files.pythonhosted.org/packages/ad/39/17180d9806a1c50197bc63b25d0f1266f745fc3b23f11439fccb3d6baa50/pyflyby-1.7.8.tar.gz", + "yanked": false + } + ] + } + "#; + let data: SimpleJson = serde_json::from_str(response).unwrap(); + let base = Url::parse("https://pypi.org/simple/pyflyby/").unwrap(); + let simple_metadata = SimpleMetadata::from_files( + data.files, + &PackageName::from_str("pyflyby").unwrap(), + &base, + ); + let versions: Vec = simple_metadata + .iter() + .map(|SimpleMetadatum { version, .. }| version.to_string()) + .collect(); + assert_eq!(versions, ["1.7.8".to_string()]); +} + +/// Test for AWS Code Artifact registry +/// +/// See: +#[test] +fn relative_urls_code_artifact() -> Result<(), JoinRelativeError> { + let text = r#" + + + + Links for flask + + +

Links for flask

+ Flask-0.1.tar.gz +
+ Flask-0.10.1.tar.gz +
+ flask-3.0.1.tar.gz +
+ + + "#; + + // Note the lack of a trailing `/` here is important for coverage of url-join behavior + let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask") + .unwrap(); + let SimpleHtml { base, files } = SimpleHtml::parse(text, &base).unwrap(); + + // Test parsing of the file urls + let urls = files + .iter() + .map(|file| uv_pypi_types::base_url_join_relative(base.as_url().as_str(), &file.url)) + .collect::, JoinRelativeError>>()?; + let urls = urls.iter().map(reqwest::Url::as_str).collect::>(); + insta::assert_debug_snapshot!(urls, @r###" + [ + "https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/0.1/Flask-0.1.tar.gz#sha256=9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", + "https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/0.10.1/Flask-0.10.1.tar.gz#sha256=4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", + "https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/3.0.1/flask-3.0.1.tar.gz#sha256=6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", + ] + "###); + + Ok(()) +} diff --git a/crates/uv-client/tests/it/main.rs b/crates/uv-client/tests/it/main.rs new file mode 100644 index 000000000..c6f2b96e8 --- /dev/null +++ b/crates/uv-client/tests/it/main.rs @@ -0,0 +1,2 @@ +mod remote_metadata; +mod user_agent_version; diff --git a/crates/uv-client/tests/remote_metadata.rs b/crates/uv-client/tests/it/remote_metadata.rs similarity index 100% rename from crates/uv-client/tests/remote_metadata.rs rename to crates/uv-client/tests/it/remote_metadata.rs diff --git a/crates/uv-client/tests/user_agent_version.rs b/crates/uv-client/tests/it/user_agent_version.rs similarity index 100% rename from crates/uv-client/tests/user_agent_version.rs rename to crates/uv-client/tests/it/user_agent_version.rs diff --git a/crates/uv-configuration/Cargo.toml b/crates/uv-configuration/Cargo.toml index 974c76165..0fa47d96d 100644 --- a/crates/uv-configuration/Cargo.toml +++ b/crates/uv-configuration/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -21,6 +24,7 @@ uv-normalize = { workspace = true } uv-pep508 = { workspace = true, features = ["schemars"] } uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } +uv-static = { workspace = true } clap = { workspace = true, features = ["derive"], optional = true } either = { workspace = true } diff --git a/crates/uv-configuration/src/bounds.rs b/crates/uv-configuration/src/bounds.rs new file mode 100644 index 000000000..c1d54ad59 --- /dev/null +++ b/crates/uv-configuration/src/bounds.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Default, Copy, Clone)] +pub enum LowerBound { + /// Allow missing lower bounds. + #[default] + Allow, + /// Warn about missing lower bounds. + Warn, +} diff --git a/crates/uv-configuration/src/build_options.rs b/crates/uv-configuration/src/build_options.rs index 1a62a1a12..cfdb807af 100644 --- a/crates/uv-configuration/src/build_options.rs +++ b/crates/uv-configuration/src/build_options.rs @@ -354,66 +354,4 @@ pub enum IndexStrategy { } #[cfg(test)] -mod tests { - use std::str::FromStr; - - use anyhow::Error; - - use super::*; - - #[test] - fn no_build_from_args() -> Result<(), Error> { - assert_eq!( - NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":all:")?], false), - NoBuild::All, - ); - assert_eq!( - NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":all:")?], true), - NoBuild::All, - ); - assert_eq!( - NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":none:")?], true), - NoBuild::All, - ); - assert_eq!( - NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":none:")?], false), - NoBuild::None, - ); - assert_eq!( - NoBuild::from_pip_args( - vec![ - PackageNameSpecifier::from_str("foo")?, - PackageNameSpecifier::from_str("bar")? - ], - false - ), - NoBuild::Packages(vec![ - PackageName::from_str("foo")?, - PackageName::from_str("bar")? - ]), - ); - assert_eq!( - NoBuild::from_pip_args( - vec![ - PackageNameSpecifier::from_str("test")?, - PackageNameSpecifier::All - ], - false - ), - NoBuild::All, - ); - assert_eq!( - NoBuild::from_pip_args( - vec![ - PackageNameSpecifier::from_str("foo")?, - PackageNameSpecifier::from_str(":none:")?, - PackageNameSpecifier::from_str("bar")? - ], - false - ), - NoBuild::Packages(vec![PackageName::from_str("bar")?]), - ); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-configuration/src/build_options/tests.rs b/crates/uv-configuration/src/build_options/tests.rs new file mode 100644 index 000000000..4eabc928b --- /dev/null +++ b/crates/uv-configuration/src/build_options/tests.rs @@ -0,0 +1,61 @@ +use std::str::FromStr; + +use anyhow::Error; + +use super::*; + +#[test] +fn no_build_from_args() -> Result<(), Error> { + assert_eq!( + NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":all:")?], false), + NoBuild::All, + ); + assert_eq!( + NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":all:")?], true), + NoBuild::All, + ); + assert_eq!( + NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":none:")?], true), + NoBuild::All, + ); + assert_eq!( + NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":none:")?], false), + NoBuild::None, + ); + assert_eq!( + NoBuild::from_pip_args( + vec![ + PackageNameSpecifier::from_str("foo")?, + PackageNameSpecifier::from_str("bar")? + ], + false + ), + NoBuild::Packages(vec![ + PackageName::from_str("foo")?, + PackageName::from_str("bar")? + ]), + ); + assert_eq!( + NoBuild::from_pip_args( + vec![ + PackageNameSpecifier::from_str("test")?, + PackageNameSpecifier::All + ], + false + ), + NoBuild::All, + ); + assert_eq!( + NoBuild::from_pip_args( + vec![ + PackageNameSpecifier::from_str("foo")?, + PackageNameSpecifier::from_str(":none:")?, + PackageNameSpecifier::from_str("bar")? + ], + false + ), + NoBuild::Packages(vec![PackageName::from_str("bar")?]), + ); + + Ok(()) +} diff --git a/crates/uv-configuration/src/config_settings.rs b/crates/uv-configuration/src/config_settings.rs index 7d8907483..a25521e49 100644 --- a/crates/uv-configuration/src/config_settings.rs +++ b/crates/uv-configuration/src/config_settings.rs @@ -213,82 +213,4 @@ impl<'de> serde::Deserialize<'de> for ConfigSettings { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn collect_config_settings() { - let settings: ConfigSettings = vec![ - ConfigSettingEntry { - key: "key".to_string(), - value: "value".to_string(), - }, - ConfigSettingEntry { - key: "key".to_string(), - value: "value2".to_string(), - }, - ConfigSettingEntry { - key: "list".to_string(), - value: "value3".to_string(), - }, - ConfigSettingEntry { - key: "list".to_string(), - value: "value4".to_string(), - }, - ] - .into_iter() - .collect(); - assert_eq!( - settings.0.get("key"), - Some(&ConfigSettingValue::List(vec![ - "value".to_string(), - "value2".to_string() - ])) - ); - assert_eq!( - settings.0.get("list"), - Some(&ConfigSettingValue::List(vec![ - "value3".to_string(), - "value4".to_string() - ])) - ); - } - - #[test] - fn escape_for_python() { - let mut settings = ConfigSettings::default(); - settings.0.insert( - "key".to_string(), - ConfigSettingValue::String("value".to_string()), - ); - settings.0.insert( - "list".to_string(), - ConfigSettingValue::List(vec!["value1".to_string(), "value2".to_string()]), - ); - assert_eq!( - settings.escape_for_python(), - r#"{"key":"value","list":["value1","value2"]}"# - ); - - let mut settings = ConfigSettings::default(); - settings.0.insert( - "key".to_string(), - ConfigSettingValue::String("Hello, \"world!\"".to_string()), - ); - settings.0.insert( - "list".to_string(), - ConfigSettingValue::List(vec!["'value1'".to_string()]), - ); - assert_eq!( - settings.escape_for_python(), - r#"{"key":"Hello, \"world!\"","list":["'value1'"]}"# - ); - - let mut settings = ConfigSettings::default(); - settings.0.insert( - "key".to_string(), - ConfigSettingValue::String("val\\1 {}value".to_string()), - ); - assert_eq!(settings.escape_for_python(), r#"{"key":"val\\1 {}value"}"#); - } -} +mod tests; diff --git a/crates/uv-configuration/src/config_settings/tests.rs b/crates/uv-configuration/src/config_settings/tests.rs new file mode 100644 index 000000000..120a331ce --- /dev/null +++ b/crates/uv-configuration/src/config_settings/tests.rs @@ -0,0 +1,77 @@ +use super::*; + +#[test] +fn collect_config_settings() { + let settings: ConfigSettings = vec![ + ConfigSettingEntry { + key: "key".to_string(), + value: "value".to_string(), + }, + ConfigSettingEntry { + key: "key".to_string(), + value: "value2".to_string(), + }, + ConfigSettingEntry { + key: "list".to_string(), + value: "value3".to_string(), + }, + ConfigSettingEntry { + key: "list".to_string(), + value: "value4".to_string(), + }, + ] + .into_iter() + .collect(); + assert_eq!( + settings.0.get("key"), + Some(&ConfigSettingValue::List(vec![ + "value".to_string(), + "value2".to_string() + ])) + ); + assert_eq!( + settings.0.get("list"), + Some(&ConfigSettingValue::List(vec![ + "value3".to_string(), + "value4".to_string() + ])) + ); +} + +#[test] +fn escape_for_python() { + let mut settings = ConfigSettings::default(); + settings.0.insert( + "key".to_string(), + ConfigSettingValue::String("value".to_string()), + ); + settings.0.insert( + "list".to_string(), + ConfigSettingValue::List(vec!["value1".to_string(), "value2".to_string()]), + ); + assert_eq!( + settings.escape_for_python(), + r#"{"key":"value","list":["value1","value2"]}"# + ); + + let mut settings = ConfigSettings::default(); + settings.0.insert( + "key".to_string(), + ConfigSettingValue::String("Hello, \"world!\"".to_string()), + ); + settings.0.insert( + "list".to_string(), + ConfigSettingValue::List(vec!["'value1'".to_string()]), + ); + assert_eq!( + settings.escape_for_python(), + r#"{"key":"Hello, \"world!\"","list":["'value1'"]}"# + ); + + let mut settings = ConfigSettings::default(); + settings.0.insert( + "key".to_string(), + ConfigSettingValue::String("val\\1 {}value".to_string()), + ); + assert_eq!(settings.escape_for_python(), r#"{"key":"val\\1 {}value"}"#); +} diff --git a/crates/uv-configuration/src/lib.rs b/crates/uv-configuration/src/lib.rs index fbd40a32b..bd45cab77 100644 --- a/crates/uv-configuration/src/lib.rs +++ b/crates/uv-configuration/src/lib.rs @@ -1,4 +1,5 @@ pub use authentication::*; +pub use bounds::*; pub use build_options::*; pub use concurrency::*; pub use config_settings::*; @@ -13,6 +14,7 @@ pub use name_specifiers::*; pub use overrides::*; pub use package_options::*; pub use preview::*; +pub use project_build_backend::*; pub use sources::*; pub use target_triple::*; pub use trusted_host::*; @@ -20,6 +22,7 @@ pub use trusted_publishing::*; pub use vcs::*; mod authentication; +mod bounds; mod build_options; mod concurrency; mod config_settings; @@ -34,6 +37,7 @@ mod name_specifiers; mod overrides; mod package_options; mod preview; +mod project_build_backend; mod sources; mod target_triple; mod trusted_host; diff --git a/crates/uv-configuration/src/project_build_backend.rs b/crates/uv-configuration/src/project_build_backend.rs new file mode 100644 index 000000000..de5623077 --- /dev/null +++ b/crates/uv-configuration/src/project_build_backend.rs @@ -0,0 +1,20 @@ +/// Available project build backends for use in `pyproject.toml`. +#[derive(Clone, Copy, Debug, PartialEq, Default, serde::Deserialize)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub enum ProjectBuildBackend { + #[default] + /// Use [hatchling](https://pypi.org/project/hatchling) as the project build backend. + Hatch, + /// Use [flit-core](https://pypi.org/project/flit-core) as the project build backend. + Flit, + /// Use [pdm-backend](https://pypi.org/project/pdm-backend) as the project build backend. + PDM, + /// Use [setuptools](https://pypi.org/project/setuptools) as the project build backend. + Setuptools, + /// Use [maturin](https://pypi.org/project/maturin) as the project build backend. + Maturin, + /// Use [scikit-build-core](https://pypi.org/project/scikit-build-core) as the project build backend. + Scikit, +} diff --git a/crates/uv-configuration/src/target_triple.rs b/crates/uv-configuration/src/target_triple.rs index f884df409..c54359303 100644 --- a/crates/uv-configuration/src/target_triple.rs +++ b/crates/uv-configuration/src/target_triple.rs @@ -2,6 +2,7 @@ use tracing::debug; use uv_pep508::MarkerEnvironment; use uv_platform_tags::{Arch, Os, Platform}; +use uv_static::EnvVars; /// The supported target triples. Each triple consists of an architecture, vendor, and operating /// system. @@ -321,7 +322,7 @@ impl TargetTriple { /// Return the macOS deployment target as parsed from the environment. fn macos_deployment_target() -> Option<(u16, u16)> { - let version = std::env::var("MACOSX_DEPLOYMENT_TARGET").ok()?; + let version = std::env::var(EnvVars::MACOSX_DEPLOYMENT_TARGET).ok()?; let mut parts = version.split('.'); // Parse the major version (e.g., `12` in `12.0`). diff --git a/crates/uv-configuration/src/trusted_host.rs b/crates/uv-configuration/src/trusted_host.rs index 4fa1bce67..326ef8602 100644 --- a/crates/uv-configuration/src/trusted_host.rs +++ b/crates/uv-configuration/src/trusted_host.rs @@ -2,34 +2,39 @@ use serde::{Deserialize, Deserializer}; use std::str::FromStr; use url::Url; -/// A trusted host, which could be a host or a host-port pair. +/// A host specification (wildcard, or host, with optional scheme and/or port) for which +/// certificates are not verified when making HTTPS requests. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct TrustedHost { - scheme: Option, - host: String, - port: Option, +pub enum TrustedHost { + Wildcard, + Host { + scheme: Option, + host: String, + port: Option, + }, } impl TrustedHost { /// Returns `true` if the [`Url`] matches this trusted host. pub fn matches(&self, url: &Url) -> bool { - if self - .scheme - .as_ref() - .is_some_and(|scheme| scheme != url.scheme()) - { - return false; - } + match self { + TrustedHost::Wildcard => true, + TrustedHost::Host { scheme, host, port } => { + if scheme.as_ref().is_some_and(|scheme| scheme != url.scheme()) { + return false; + } - if self.port.is_some_and(|port| url.port() != Some(port)) { - return false; - } + if port.is_some_and(|port| url.port() != Some(port)) { + return false; + } - if Some(self.host.as_ref()) != url.host_str() { - return false; - } + if Some(host.as_str()) != url.host_str() { + return false; + } - true + true + } + } } } @@ -48,7 +53,7 @@ impl<'de> Deserialize<'de> for TrustedHost { serde_untagged::UntaggedEnumVisitor::new() .string(|string| TrustedHost::from_str(string).map_err(serde::de::Error::custom)) .map(|map| { - map.deserialize::().map(|inner| TrustedHost { + map.deserialize::().map(|inner| TrustedHost::Host { scheme: inner.scheme, host: inner.host, port: inner.port, @@ -80,6 +85,10 @@ impl std::str::FromStr for TrustedHost { type Err = TrustedHostError; fn from_str(s: &str) -> Result { + if s == "*" { + return Ok(Self::Wildcard); + } + // Detect scheme. let (scheme, s) = if let Some(s) = s.strip_prefix("https://") { (Some("https".to_string()), s) @@ -105,20 +114,27 @@ impl std::str::FromStr for TrustedHost { .transpose() .map_err(|_| TrustedHostError::InvalidPort(s.to_string()))?; - Ok(Self { scheme, host, port }) + Ok(Self::Host { scheme, host, port }) } } impl std::fmt::Display for TrustedHost { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if let Some(scheme) = &self.scheme { - write!(f, "{}://{}", scheme, self.host)?; - } else { - write!(f, "{}", self.host)?; - } + match self { + TrustedHost::Wildcard => { + write!(f, "*")?; + } + TrustedHost::Host { scheme, host, port } => { + if let Some(scheme) = &scheme { + write!(f, "{scheme}://{host}")?; + } else { + write!(f, "{host}")?; + } - if let Some(port) = self.port { - write!(f, ":{port}")?; + if let Some(port) = port { + write!(f, ":{port}")?; + } + } } Ok(()) @@ -145,45 +161,4 @@ impl schemars::JsonSchema for TrustedHost { } #[cfg(test)] -mod tests { - #[test] - fn parse() { - assert_eq!( - "example.com".parse::().unwrap(), - super::TrustedHost { - scheme: None, - host: "example.com".to_string(), - port: None - } - ); - - assert_eq!( - "example.com:8080".parse::().unwrap(), - super::TrustedHost { - scheme: None, - host: "example.com".to_string(), - port: Some(8080) - } - ); - - assert_eq!( - "https://example.com".parse::().unwrap(), - super::TrustedHost { - scheme: Some("https".to_string()), - host: "example.com".to_string(), - port: None - } - ); - - assert_eq!( - "https://example.com/hello/world" - .parse::() - .unwrap(), - super::TrustedHost { - scheme: Some("https".to_string()), - host: "example.com".to_string(), - port: None - } - ); - } -} +mod tests; diff --git a/crates/uv-configuration/src/trusted_host/tests.rs b/crates/uv-configuration/src/trusted_host/tests.rs new file mode 100644 index 000000000..6eef745a9 --- /dev/null +++ b/crates/uv-configuration/src/trusted_host/tests.rs @@ -0,0 +1,45 @@ +#[test] +fn parse() { + assert_eq!( + "*".parse::().unwrap(), + super::TrustedHost::Wildcard + ); + + assert_eq!( + "example.com".parse::().unwrap(), + super::TrustedHost::Host { + scheme: None, + host: "example.com".to_string(), + port: None + } + ); + + assert_eq!( + "example.com:8080".parse::().unwrap(), + super::TrustedHost::Host { + scheme: None, + host: "example.com".to_string(), + port: Some(8080) + } + ); + + assert_eq!( + "https://example.com".parse::().unwrap(), + super::TrustedHost::Host { + scheme: Some("https".to_string()), + host: "example.com".to_string(), + port: None + } + ); + + assert_eq!( + "https://example.com/hello/world" + .parse::() + .unwrap(), + super::TrustedHost::Host { + scheme: Some("https".to_string()), + host: "example.com".to_string(), + port: None + } + ); +} diff --git a/crates/uv-console/Cargo.toml b/crates/uv-console/Cargo.toml index ef0303f71..597105ee9 100644 --- a/crates/uv-console/Cargo.toml +++ b/crates/uv-console/Cargo.toml @@ -4,6 +4,9 @@ version = "0.0.1" edition = "2021" description = "Utilities for interacting with the terminal" +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-console/src/lib.rs b/crates/uv-console/src/lib.rs index 83d50f0cd..165edd1de 100644 --- a/crates/uv-console/src/lib.rs +++ b/crates/uv-console/src/lib.rs @@ -1,4 +1,5 @@ -use console::{style, Key, Term}; +use console::{measure_text_width, style, Key, Term}; +use std::{cmp::Ordering, iter}; /// Prompt the user for confirmation in the given [`Term`]. /// @@ -72,3 +73,190 @@ pub fn confirm(message: &str, term: &Term, default: bool) -> std::io::Result std::io::Result { + term.write_str(prompt)?; + term.show_cursor()?; + term.flush()?; + + let input = term.read_secure_line()?; + + term.clear_line()?; + + Ok(input) +} + +/// Prompt the user for input text in the given [`Term`]. +/// +/// This is a slimmed-down version of `dialoguer::Input`. +#[allow( + // Suppress Clippy lints triggered by `dialoguer::Input`. + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_sign_loss +)] +pub fn input(prompt: &str, term: &Term) -> std::io::Result { + term.write_str(prompt)?; + term.show_cursor()?; + term.flush()?; + + let prompt_len = measure_text_width(prompt); + + let mut chars: Vec = Vec::new(); + let mut position = 0; + loop { + match term.read_key()? { + Key::Backspace if position > 0 => { + position -= 1; + chars.remove(position); + let line_size = term.size().1 as usize; + // Case we want to delete last char of a line so the cursor is at the beginning of the next line + if (position + prompt_len) % (line_size - 1) == 0 { + term.clear_line()?; + term.move_cursor_up(1)?; + term.move_cursor_right(line_size + 1)?; + } else { + term.clear_chars(1)?; + } + + let tail: String = chars[position..].iter().collect(); + + if !tail.is_empty() { + term.write_str(&tail)?; + + let total = position + prompt_len + tail.chars().count(); + let total_line = total / line_size; + let line_cursor = (position + prompt_len) / line_size; + term.move_cursor_up(total_line - line_cursor)?; + + term.move_cursor_left(line_size)?; + term.move_cursor_right((position + prompt_len) % line_size)?; + } + + term.flush()?; + } + Key::Char(chr) if !chr.is_ascii_control() => { + chars.insert(position, chr); + position += 1; + let tail: String = iter::once(&chr).chain(chars[position..].iter()).collect(); + term.write_str(&tail)?; + term.move_cursor_left(tail.chars().count() - 1)?; + term.flush()?; + } + Key::ArrowLeft if position > 0 => { + if (position + prompt_len) % term.size().1 as usize == 0 { + term.move_cursor_up(1)?; + term.move_cursor_right(term.size().1 as usize)?; + } else { + term.move_cursor_left(1)?; + } + position -= 1; + term.flush()?; + } + Key::ArrowRight if position < chars.len() => { + if (position + prompt_len) % (term.size().1 as usize - 1) == 0 { + term.move_cursor_down(1)?; + term.move_cursor_left(term.size().1 as usize)?; + } else { + term.move_cursor_right(1)?; + } + position += 1; + term.flush()?; + } + Key::UnknownEscSeq(seq) if seq == vec!['b'] => { + let line_size = term.size().1 as usize; + let nb_space = chars[..position] + .iter() + .rev() + .take_while(|c| c.is_whitespace()) + .count(); + let find_last_space = chars[..position - nb_space] + .iter() + .rposition(|c| c.is_whitespace()); + + // If we find a space we set the cursor to the next char else we set it to the beginning of the input + if let Some(mut last_space) = find_last_space { + if last_space < position { + last_space += 1; + let new_line = (prompt_len + last_space) / line_size; + let old_line = (prompt_len + position) / line_size; + let diff_line = old_line - new_line; + if diff_line != 0 { + term.move_cursor_up(old_line - new_line)?; + } + + let new_pos_x = (prompt_len + last_space) % line_size; + let old_pos_x = (prompt_len + position) % line_size; + let diff_pos_x = new_pos_x as i64 - old_pos_x as i64; + if diff_pos_x < 0 { + term.move_cursor_left(-diff_pos_x as usize)?; + } else { + term.move_cursor_right((diff_pos_x) as usize)?; + } + position = last_space; + } + } else { + term.move_cursor_left(position)?; + position = 0; + } + + term.flush()?; + } + Key::UnknownEscSeq(seq) if seq == vec!['f'] => { + let line_size = term.size().1 as usize; + let find_next_space = chars[position..].iter().position(|c| c.is_whitespace()); + + // If we find a space we set the cursor to the next char else we set it to the beginning of the input + if let Some(mut next_space) = find_next_space { + let nb_space = chars[position + next_space..] + .iter() + .take_while(|c| c.is_whitespace()) + .count(); + next_space += nb_space; + let new_line = (prompt_len + position + next_space) / line_size; + let old_line = (prompt_len + position) / line_size; + term.move_cursor_down(new_line - old_line)?; + + let new_pos_x = (prompt_len + position + next_space) % line_size; + let old_pos_x = (prompt_len + position) % line_size; + let diff_pos_x = new_pos_x as i64 - old_pos_x as i64; + if diff_pos_x < 0 { + term.move_cursor_left(-diff_pos_x as usize)?; + } else { + term.move_cursor_right((diff_pos_x) as usize)?; + } + position += next_space; + } else { + let new_line = (prompt_len + chars.len()) / line_size; + let old_line = (prompt_len + position) / line_size; + term.move_cursor_down(new_line - old_line)?; + + let new_pos_x = (prompt_len + chars.len()) % line_size; + let old_pos_x = (prompt_len + position) % line_size; + let diff_pos_x = new_pos_x as i64 - old_pos_x as i64; + match diff_pos_x.cmp(&0) { + Ordering::Less => { + term.move_cursor_left((-diff_pos_x - 1) as usize)?; + } + Ordering::Equal => {} + Ordering::Greater => { + term.move_cursor_right((diff_pos_x) as usize)?; + } + } + position = chars.len(); + } + + term.flush()?; + } + Key::Enter => break, + _ => (), + } + } + let input = chars.iter().collect::(); + term.write_line("")?; + + Ok(input) +} diff --git a/crates/uv-dev/Cargo.toml b/crates/uv-dev/Cargo.toml index 2193afa2a..2a436267f 100644 --- a/crates/uv-dev/Cargo.toml +++ b/crates/uv-dev/Cargo.toml @@ -28,6 +28,7 @@ uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } uv-python = { workspace = true } uv-settings = { workspace = true, features = ["schemars"] } +uv-static = { workspace = true } uv-workspace = { workspace = true, features = ["schemars"] } # Any dependencies that are exclusively used in `uv-dev` should be listed as non-workspace diff --git a/crates/uv-dev/src/generate_cli_reference.rs b/crates/uv-dev/src/generate_cli_reference.rs index 0d743ecd9..f036053ce 100644 --- a/crates/uv-dev/src/generate_cli_reference.rs +++ b/crates/uv-dev/src/generate_cli_reference.rs @@ -324,22 +324,4 @@ fn emit_possible_options(opt: &clap::Arg, output: &mut String) { } #[cfg(test)] -mod tests { - use std::env; - - use anyhow::Result; - - use crate::generate_all::Mode; - - use super::{main, Args}; - - #[test] - fn test_generate_cli_reference() -> Result<()> { - let mode = if env::var("UV_UPDATE_SCHEMA").as_deref() == Ok("1") { - Mode::Write - } else { - Mode::Check - }; - main(&Args { mode }) - } -} +mod tests; diff --git a/crates/uv-dev/src/generate_cli_reference/tests.rs b/crates/uv-dev/src/generate_cli_reference/tests.rs new file mode 100644 index 000000000..ee626eb35 --- /dev/null +++ b/crates/uv-dev/src/generate_cli_reference/tests.rs @@ -0,0 +1,19 @@ +use std::env; + +use anyhow::Result; + +use uv_static::EnvVars; + +use crate::generate_all::Mode; + +use super::{main, Args}; + +#[test] +fn test_generate_cli_reference() -> Result<()> { + let mode = if env::var(EnvVars::UV_UPDATE_SCHEMA).as_deref() == Ok("1") { + Mode::Write + } else { + Mode::Check + }; + main(&Args { mode }) +} diff --git a/crates/uv-dev/src/generate_json_schema.rs b/crates/uv-dev/src/generate_json_schema.rs index 7eda12cf8..2fcf5d5d8 100644 --- a/crates/uv-dev/src/generate_json_schema.rs +++ b/crates/uv-dev/src/generate_json_schema.rs @@ -81,22 +81,4 @@ pub(crate) fn main(args: &Args) -> Result<()> { } #[cfg(test)] -mod tests { - use std::env; - - use anyhow::Result; - - use crate::generate_all::Mode; - - use super::{main, Args}; - - #[test] - fn test_generate_json_schema() -> Result<()> { - let mode = if env::var("UV_UPDATE_SCHEMA").as_deref() == Ok("1") { - Mode::Write - } else { - Mode::Check - }; - main(&Args { mode }) - } -} +mod tests; diff --git a/crates/uv-dev/src/generate_json_schema/tests.rs b/crates/uv-dev/src/generate_json_schema/tests.rs new file mode 100644 index 000000000..137172e5e --- /dev/null +++ b/crates/uv-dev/src/generate_json_schema/tests.rs @@ -0,0 +1,19 @@ +use std::env; + +use anyhow::Result; + +use uv_static::EnvVars; + +use crate::generate_all::Mode; + +use super::{main, Args}; + +#[test] +fn test_generate_json_schema() -> Result<()> { + let mode = if env::var(EnvVars::UV_UPDATE_SCHEMA).as_deref() == Ok("1") { + Mode::Write + } else { + Mode::Check + }; + main(&Args { mode }) +} diff --git a/crates/uv-dev/src/generate_options_reference.rs b/crates/uv-dev/src/generate_options_reference.rs index 4884de9e1..0e066d9af 100644 --- a/crates/uv-dev/src/generate_options_reference.rs +++ b/crates/uv-dev/src/generate_options_reference.rs @@ -350,22 +350,4 @@ impl Visit for CollectOptionsVisitor { } #[cfg(test)] -mod tests { - use std::env; - - use anyhow::Result; - - use crate::generate_all::Mode; - - use super::{main, Args}; - - #[test] - fn test_generate_options_reference() -> Result<()> { - let mode = if env::var("UV_UPDATE_SCHEMA").as_deref() == Ok("1") { - Mode::Write - } else { - Mode::Check - }; - main(&Args { mode }) - } -} +mod tests; diff --git a/crates/uv-dev/src/generate_options_reference/tests.rs b/crates/uv-dev/src/generate_options_reference/tests.rs new file mode 100644 index 000000000..dfcdd952f --- /dev/null +++ b/crates/uv-dev/src/generate_options_reference/tests.rs @@ -0,0 +1,19 @@ +use std::env; + +use anyhow::Result; + +use uv_static::EnvVars; + +use crate::generate_all::Mode; + +use super::{main, Args}; + +#[test] +fn test_generate_options_reference() -> Result<()> { + let mode = if env::var(EnvVars::UV_UPDATE_SCHEMA).as_deref() == Ok("1") { + Mode::Write + } else { + Mode::Check + }; + main(&Args { mode }) +} diff --git a/crates/uv-dev/src/main.rs b/crates/uv-dev/src/main.rs index 1d7c7b186..9e6d29fc0 100644 --- a/crates/uv-dev/src/main.rs +++ b/crates/uv-dev/src/main.rs @@ -25,6 +25,7 @@ use crate::generate_options_reference::Args as GenerateOptionsReferenceArgs; #[cfg(feature = "render")] use crate::render_benchmarks::RenderBenchmarksArgs; use crate::wheel_metadata::WheelMetadataArgs; +use uv_static::EnvVars; mod clear_compile; mod compile; @@ -77,7 +78,7 @@ async fn run() -> Result<()> { #[tokio::main(flavor = "current_thread")] async fn main() -> ExitCode { - let (duration_layer, _guard) = if let Ok(location) = env::var("TRACING_DURATIONS_FILE") { + let (duration_layer, _guard) = if let Ok(location) = env::var(EnvVars::TRACING_DURATIONS_FILE) { let location = PathBuf::from(location); if let Some(parent) = location.parent() { fs_err::tokio::create_dir_all(&parent) diff --git a/crates/uv-dispatch/Cargo.toml b/crates/uv-dispatch/Cargo.toml index 638caa7aa..b61a7534b 100644 --- a/crates/uv-dispatch/Cargo.toml +++ b/crates/uv-dispatch/Cargo.toml @@ -10,6 +10,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index 69e894c0b..6fc529a28 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -15,7 +15,8 @@ use uv_build_frontend::{SourceBuild, SourceBuildContext}; use uv_cache::Cache; use uv_client::RegistryClient; use uv_configuration::{ - BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, Reinstall, SourceStrategy, + BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, LowerBound, Reinstall, + SourceStrategy, }; use uv_configuration::{BuildOutput, Concurrency}; use uv_distribution::DistributionDatabase; @@ -28,8 +29,8 @@ use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages}; use uv_pypi_types::Requirement; use uv_python::{Interpreter, PythonEnvironment}; use uv_resolver::{ - ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver, - ResolverMarkers, + ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest, OptionsBuilder, + PythonRequirement, Resolver, ResolverMarkers, }; use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; @@ -56,6 +57,7 @@ pub struct BuildDispatch<'a> { exclude_newer: Option, source_build_context: SourceBuildContext, build_extra_env_vars: FxHashMap, + bounds: LowerBound, sources: SourceStrategy, concurrency: Concurrency, } @@ -80,6 +82,7 @@ impl<'a> BuildDispatch<'a> { build_options: &'a BuildOptions, hasher: &'a HashStrategy, exclude_newer: Option, + bounds: LowerBound, sources: SourceStrategy, concurrency: Concurrency, ) -> Self { @@ -104,6 +107,7 @@ impl<'a> BuildDispatch<'a> { exclude_newer, source_build_context: SourceBuildContext::default(), build_extra_env_vars: FxHashMap::default(), + bounds, sources, concurrency, } @@ -152,11 +156,15 @@ impl<'a> BuildContext for BuildDispatch<'a> { self.config_settings } + fn bounds(&self) -> LowerBound { + self.bounds + } + fn sources(&self) -> SourceStrategy { self.sources } - fn index_locations(&self) -> &IndexLocations { + fn locations(&self) -> &IndexLocations { self.index_locations } @@ -170,6 +178,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { OptionsBuilder::new() .exclude_newer(self.exclude_newer) .index_strategy(self.index_strategy) + .flexibility(Flexibility::Fixed) .build(), &python_requirement, ResolverMarkers::specific_environment(markers), @@ -228,7 +237,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { } = Planner::new(resolution).build( site_packages, &Reinstall::default(), - &BuildOptions::default(), + self.build_options, self.hasher, self.index_locations, self.config_settings, @@ -309,8 +318,10 @@ impl<'a> BuildContext for BuildDispatch<'a> { &'data self, source: &'data Path, subdirectory: Option<&'data Path>, + install_path: &'data Path, version_id: Option, dist: Option<&'data SourceDist>, + sources: SourceStrategy, build_kind: BuildKind, build_output: BuildOutput, ) -> Result { @@ -342,12 +353,15 @@ impl<'a> BuildContext for BuildDispatch<'a> { let builder = SourceBuild::setup( source, subdirectory, + install_path, dist_name, dist_version, self.interpreter, self, self.source_build_context.clone(), version_id, + self.index_locations, + sources, self.config_settings.clone(), self.build_isolation, build_kind, diff --git a/crates/uv-distribution-filename/Cargo.toml b/crates/uv-distribution-filename/Cargo.toml index 5411b75cb..b8484b97e 100644 --- a/crates/uv-distribution-filename/Cargo.toml +++ b/crates/uv-distribution-filename/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-distribution-filename/src/egg.rs b/crates/uv-distribution-filename/src/egg.rs index f891169a3..84bbf3b64 100644 --- a/crates/uv-distribution-filename/src/egg.rs +++ b/crates/uv-distribution-filename/src/egg.rs @@ -80,38 +80,4 @@ impl FromStr for EggInfoFilename { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn egg_info_filename() { - let filename = "zstandard-0.22.0-py3.12-darwin.egg-info"; - let parsed = EggInfoFilename::from_str(filename).unwrap(); - assert_eq!(parsed.name.as_ref(), "zstandard"); - assert_eq!( - parsed.version.map(|v| v.to_string()), - Some("0.22.0".to_string()) - ); - - let filename = "zstandard-0.22.0-py3.12.egg-info"; - let parsed = EggInfoFilename::from_str(filename).unwrap(); - assert_eq!(parsed.name.as_ref(), "zstandard"); - assert_eq!( - parsed.version.map(|v| v.to_string()), - Some("0.22.0".to_string()) - ); - - let filename = "zstandard-0.22.0.egg-info"; - let parsed = EggInfoFilename::from_str(filename).unwrap(); - assert_eq!(parsed.name.as_ref(), "zstandard"); - assert_eq!( - parsed.version.map(|v| v.to_string()), - Some("0.22.0".to_string()) - ); - - let filename = "zstandard.egg-info"; - let parsed = EggInfoFilename::from_str(filename).unwrap(); - assert_eq!(parsed.name.as_ref(), "zstandard"); - assert!(parsed.version.is_none()); - } -} +mod tests; diff --git a/crates/uv-distribution-filename/src/egg/tests.rs b/crates/uv-distribution-filename/src/egg/tests.rs new file mode 100644 index 000000000..47c4dd56b --- /dev/null +++ b/crates/uv-distribution-filename/src/egg/tests.rs @@ -0,0 +1,33 @@ +use super::*; + +#[test] +fn egg_info_filename() { + let filename = "zstandard-0.22.0-py3.12-darwin.egg-info"; + let parsed = EggInfoFilename::from_str(filename).unwrap(); + assert_eq!(parsed.name.as_ref(), "zstandard"); + assert_eq!( + parsed.version.map(|v| v.to_string()), + Some("0.22.0".to_string()) + ); + + let filename = "zstandard-0.22.0-py3.12.egg-info"; + let parsed = EggInfoFilename::from_str(filename).unwrap(); + assert_eq!(parsed.name.as_ref(), "zstandard"); + assert_eq!( + parsed.version.map(|v| v.to_string()), + Some("0.22.0".to_string()) + ); + + let filename = "zstandard-0.22.0.egg-info"; + let parsed = EggInfoFilename::from_str(filename).unwrap(); + assert_eq!(parsed.name.as_ref(), "zstandard"); + assert_eq!( + parsed.version.map(|v| v.to_string()), + Some("0.22.0".to_string()) + ); + + let filename = "zstandard.egg-info"; + let parsed = EggInfoFilename::from_str(filename).unwrap(); + assert_eq!(parsed.name.as_ref(), "zstandard"); + assert!(parsed.version.is_none()); +} diff --git a/crates/uv-distribution-filename/src/source_dist.rs b/crates/uv-distribution-filename/src/source_dist.rs index a3920a32d..2c1c26740 100644 --- a/crates/uv-distribution-filename/src/source_dist.rs +++ b/crates/uv-distribution-filename/src/source_dist.rs @@ -170,58 +170,4 @@ enum SourceDistFilenameErrorKind { } #[cfg(test)] -mod tests { - use std::str::FromStr; - - use uv_normalize::PackageName; - - use crate::{SourceDistExtension, SourceDistFilename}; - - /// Only test already normalized names since the parsing is lossy - /// - /// - /// - #[test] - fn roundtrip() { - for normalized in [ - "foo_lib-1.2.3.zip", - "foo_lib-1.2.3a3.zip", - "foo_lib-1.2.3.tar.gz", - "foo_lib-1.2.3.tar.bz2", - "foo_lib-1.2.3.tar.zst", - ] { - let ext = SourceDistExtension::from_path(normalized).unwrap(); - assert_eq!( - SourceDistFilename::parse( - normalized, - ext, - &PackageName::from_str("foo_lib").unwrap() - ) - .unwrap() - .to_string(), - normalized - ); - } - } - - #[test] - fn errors() { - for invalid in ["b-1.2.3.zip", "a-1.2.3-gamma.3.zip"] { - let ext = SourceDistExtension::from_path(invalid).unwrap(); - assert!( - SourceDistFilename::parse(invalid, ext, &PackageName::from_str("a").unwrap()) - .is_err() - ); - } - } - - #[test] - fn name_too_long() { - assert!(SourceDistFilename::parse( - "foo.zip", - SourceDistExtension::Zip, - &PackageName::from_str("foo-lib").unwrap() - ) - .is_err()); - } -} +mod tests; diff --git a/crates/uv-distribution-filename/src/source_dist/tests.rs b/crates/uv-distribution-filename/src/source_dist/tests.rs new file mode 100644 index 000000000..f81f089f1 --- /dev/null +++ b/crates/uv-distribution-filename/src/source_dist/tests.rs @@ -0,0 +1,48 @@ +use std::str::FromStr; + +use uv_normalize::PackageName; + +use crate::{SourceDistExtension, SourceDistFilename}; + +/// Only test already normalized names since the parsing is lossy +/// +/// +/// +#[test] +fn roundtrip() { + for normalized in [ + "foo_lib-1.2.3.zip", + "foo_lib-1.2.3a3.zip", + "foo_lib-1.2.3.tar.gz", + "foo_lib-1.2.3.tar.bz2", + "foo_lib-1.2.3.tar.zst", + ] { + let ext = SourceDistExtension::from_path(normalized).unwrap(); + assert_eq!( + SourceDistFilename::parse(normalized, ext, &PackageName::from_str("foo_lib").unwrap()) + .unwrap() + .to_string(), + normalized + ); + } +} + +#[test] +fn errors() { + for invalid in ["b-1.2.3.zip", "a-1.2.3-gamma.3.zip"] { + let ext = SourceDistExtension::from_path(invalid).unwrap(); + assert!( + SourceDistFilename::parse(invalid, ext, &PackageName::from_str("a").unwrap()).is_err() + ); + } +} + +#[test] +fn name_too_long() { + assert!(SourceDistFilename::parse( + "foo.zip", + SourceDistExtension::Zip, + &PackageName::from_str("foo-lib").unwrap() + ) + .is_err()); +} diff --git a/crates/uv-distribution-filename/src/wheel.rs b/crates/uv-distribution-filename/src/wheel.rs index a91c3e141..09b675b11 100644 --- a/crates/uv-distribution-filename/src/wheel.rs +++ b/crates/uv-distribution-filename/src/wheel.rs @@ -234,101 +234,4 @@ pub enum WheelFilenameError { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn err_not_whl_extension() { - let err = WheelFilename::from_str("foo.rs").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo.rs" is invalid: Must end with .whl"###); - } - - #[test] - fn err_1_part_empty() { - let err = WheelFilename::from_str(".whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename ".whl" is invalid: Must have a version"###); - } - - #[test] - fn err_1_part_no_version() { - let err = WheelFilename::from_str("foo.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo.whl" is invalid: Must have a version"###); - } - - #[test] - fn err_2_part_no_pythontag() { - let err = WheelFilename::from_str("foo-version.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo-version.whl" is invalid: Must have a Python tag"###); - } - - #[test] - fn err_3_part_no_abitag() { - let err = WheelFilename::from_str("foo-version-python.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo-version-python.whl" is invalid: Must have an ABI tag"###); - } - - #[test] - fn err_4_part_no_platformtag() { - let err = WheelFilename::from_str("foo-version-python-abi.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo-version-python-abi.whl" is invalid: Must have a platform tag"###); - } - - #[test] - fn err_too_many_parts() { - let err = - WheelFilename::from_str("foo-1.2.3-build-python-abi-platform-oops.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-build-python-abi-platform-oops.whl" is invalid: Must have 5 or 6 components, but has more"###); - } - - #[test] - fn err_invalid_package_name() { - let err = WheelFilename::from_str("f!oo-1.2.3-python-abi-platform.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "f!oo-1.2.3-python-abi-platform.whl" has an invalid package name"###); - } - - #[test] - fn err_invalid_version() { - let err = WheelFilename::from_str("foo-x.y.z-python-abi-platform.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo-x.y.z-python-abi-platform.whl" has an invalid version: expected version to start with a number, but no leading ASCII digits were found"###); - } - - #[test] - fn err_invalid_build_tag() { - let err = WheelFilename::from_str("foo-1.2.3-tag-python-abi-platform.whl").unwrap_err(); - insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-tag-python-abi-platform.whl" has an invalid build tag: must start with a digit"###); - } - - #[test] - fn ok_single_tags() { - insta::assert_debug_snapshot!(WheelFilename::from_str("foo-1.2.3-foo-bar-baz.whl")); - } - - #[test] - fn ok_multiple_tags() { - insta::assert_debug_snapshot!(WheelFilename::from_str( - "foo-1.2.3-ab.cd.ef-gh-ij.kl.mn.op.qr.st.whl" - )); - } - - #[test] - fn ok_build_tag() { - insta::assert_debug_snapshot!(WheelFilename::from_str( - "foo-1.2.3-202206090410-python-abi-platform.whl" - )); - } - - #[test] - fn from_and_to_string() { - let wheel_names = &[ - "django_allauth-0.51.0-py3-none-any.whl", - "osm2geojson-0.2.4-py3-none-any.whl", - "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", - ]; - for wheel_name in wheel_names { - assert_eq!( - WheelFilename::from_str(wheel_name).unwrap().to_string(), - *wheel_name - ); - } - } -} +mod tests; diff --git a/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap b/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap new file mode 100644 index 000000000..ee6b96c71 --- /dev/null +++ b/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap @@ -0,0 +1,27 @@ +--- +source: crates/uv-distribution-filename/src/wheel/tests.rs +expression: "WheelFilename::from_str(\"foo-1.2.3-202206090410-python-abi-platform.whl\")" +--- +Ok( + WheelFilename { + name: PackageName( + "foo", + ), + version: "1.2.3", + build_tag: Some( + BuildTag( + 202206090410, + None, + ), + ), + python_tag: [ + "python", + ], + abi_tag: [ + "abi", + ], + platform_tag: [ + "platform", + ], + }, +) diff --git a/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap b/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap new file mode 100644 index 000000000..811974f8b --- /dev/null +++ b/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap @@ -0,0 +1,29 @@ +--- +source: crates/uv-distribution-filename/src/wheel/tests.rs +expression: "WheelFilename::from_str(\"foo-1.2.3-ab.cd.ef-gh-ij.kl.mn.op.qr.st.whl\")" +--- +Ok( + WheelFilename { + name: PackageName( + "foo", + ), + version: "1.2.3", + build_tag: None, + python_tag: [ + "ab", + "cd", + "ef", + ], + abi_tag: [ + "gh", + ], + platform_tag: [ + "ij", + "kl", + "mn", + "op", + "qr", + "st", + ], + }, +) diff --git a/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_single_tags.snap b/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_single_tags.snap new file mode 100644 index 000000000..ddd909a31 --- /dev/null +++ b/crates/uv-distribution-filename/src/wheel/snapshots/uv_distribution_filename__wheel__tests__ok_single_tags.snap @@ -0,0 +1,22 @@ +--- +source: crates/uv-distribution-filename/src/wheel/tests.rs +expression: "WheelFilename::from_str(\"foo-1.2.3-foo-bar-baz.whl\")" +--- +Ok( + WheelFilename { + name: PackageName( + "foo", + ), + version: "1.2.3", + build_tag: None, + python_tag: [ + "foo", + ], + abi_tag: [ + "bar", + ], + platform_tag: [ + "baz", + ], + }, +) diff --git a/crates/uv-distribution-filename/src/wheel/tests.rs b/crates/uv-distribution-filename/src/wheel/tests.rs new file mode 100644 index 000000000..59c94e4a3 --- /dev/null +++ b/crates/uv-distribution-filename/src/wheel/tests.rs @@ -0,0 +1,95 @@ +use super::*; + +#[test] +fn err_not_whl_extension() { + let err = WheelFilename::from_str("foo.rs").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "foo.rs" is invalid: Must end with .whl"###); +} + +#[test] +fn err_1_part_empty() { + let err = WheelFilename::from_str(".whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename ".whl" is invalid: Must have a version"###); +} + +#[test] +fn err_1_part_no_version() { + let err = WheelFilename::from_str("foo.whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "foo.whl" is invalid: Must have a version"###); +} + +#[test] +fn err_2_part_no_pythontag() { + let err = WheelFilename::from_str("foo-version.whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "foo-version.whl" is invalid: Must have a Python tag"###); +} + +#[test] +fn err_3_part_no_abitag() { + let err = WheelFilename::from_str("foo-version-python.whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "foo-version-python.whl" is invalid: Must have an ABI tag"###); +} + +#[test] +fn err_4_part_no_platformtag() { + let err = WheelFilename::from_str("foo-version-python-abi.whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "foo-version-python-abi.whl" is invalid: Must have a platform tag"###); +} + +#[test] +fn err_too_many_parts() { + let err = WheelFilename::from_str("foo-1.2.3-build-python-abi-platform-oops.whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-build-python-abi-platform-oops.whl" is invalid: Must have 5 or 6 components, but has more"###); +} + +#[test] +fn err_invalid_package_name() { + let err = WheelFilename::from_str("f!oo-1.2.3-python-abi-platform.whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "f!oo-1.2.3-python-abi-platform.whl" has an invalid package name"###); +} + +#[test] +fn err_invalid_version() { + let err = WheelFilename::from_str("foo-x.y.z-python-abi-platform.whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "foo-x.y.z-python-abi-platform.whl" has an invalid version: expected version to start with a number, but no leading ASCII digits were found"###); +} + +#[test] +fn err_invalid_build_tag() { + let err = WheelFilename::from_str("foo-1.2.3-tag-python-abi-platform.whl").unwrap_err(); + insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-tag-python-abi-platform.whl" has an invalid build tag: must start with a digit"###); +} + +#[test] +fn ok_single_tags() { + insta::assert_debug_snapshot!(WheelFilename::from_str("foo-1.2.3-foo-bar-baz.whl")); +} + +#[test] +fn ok_multiple_tags() { + insta::assert_debug_snapshot!(WheelFilename::from_str( + "foo-1.2.3-ab.cd.ef-gh-ij.kl.mn.op.qr.st.whl" + )); +} + +#[test] +fn ok_build_tag() { + insta::assert_debug_snapshot!(WheelFilename::from_str( + "foo-1.2.3-202206090410-python-abi-platform.whl" + )); +} + +#[test] +fn from_and_to_string() { + let wheel_names = &[ + "django_allauth-0.51.0-py3-none-any.whl", + "osm2geojson-0.2.4-py3-none-any.whl", + "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + ]; + for wheel_name in wheel_names { + assert_eq!( + WheelFilename::from_str(wheel_name).unwrap().to_string(), + *wheel_name + ); + } +} diff --git a/crates/uv-distribution-types/Cargo.toml b/crates/uv-distribution-types/Cargo.toml index 6752ec66a..92b9c1871 100644 --- a/crates/uv-distribution-types/Cargo.toml +++ b/crates/uv-distribution-types/Cargo.toml @@ -9,10 +9,14 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true [dependencies] +uv-auth = { workspace = true } uv-cache-info = { workspace = true } uv-cache-key = { workspace = true } uv-distribution-filename = { workspace = true } @@ -25,6 +29,7 @@ uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } anyhow = { workspace = true } +bitflags = { workspace = true } fs-err = { workspace = true } itertools = { workspace = true } jiff = { workspace = true } diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs new file mode 100644 index 000000000..3e90bacf4 --- /dev/null +++ b/crates/uv-distribution-types/src/index.rs @@ -0,0 +1,192 @@ +use std::str::FromStr; +use thiserror::Error; +use url::Url; +use uv_auth::Credentials; + +use crate::origin::Origin; +use crate::{IndexUrl, IndexUrlError}; + +#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct Index { + /// The name of the index. + /// + /// Index names can be used to reference indexes elsewhere in the configuration. For example, + /// you can pin a package to a specific index by name: + /// + /// ```toml + /// [[tool.uv.index]] + /// name = "pytorch" + /// url = "https://download.pytorch.org/whl/cu121" + /// + /// [tool.uv.sources] + /// torch = { index = "pytorch" } + /// ``` + pub name: Option, + /// The URL of the index. + /// + /// Expects to receive a URL (e.g., `https://pypi.org/simple`) or a local path. + pub url: IndexUrl, + /// Mark the index as explicit. + /// + /// Explicit indexes will _only_ be used when explicitly requested via a `[tool.uv.sources]` + /// definition, as in: + /// + /// ```toml + /// [[tool.uv.index]] + /// name = "pytorch" + /// url = "https://download.pytorch.org/whl/cu121" + /// explicit = true + /// + /// [tool.uv.sources] + /// torch = { index = "pytorch" } + /// ``` + #[serde(default)] + pub explicit: bool, + /// Mark the index as the default index. + /// + /// By default, uv uses PyPI as the default index, such that even if additional indexes are + /// defined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that + /// aren't found elsewhere. To disable the PyPI default, set `default = true` on at least one + /// other index. + /// + /// Marking an index as default will move it to the front of the list of indexes, such that it + /// is given the highest priority when resolving packages. + #[serde(default)] + pub default: bool, + /// The origin of the index (e.g., a CLI flag, a user-level configuration file, etc.). + #[serde(skip)] + pub origin: Option, + // /// The type of the index. + // /// + // /// Indexes can either be PEP 503-compliant (i.e., a registry implementing the Simple API) or + // /// structured as a flat list of distributions (e.g., `--find-links`). In both cases, indexes + // /// can point to either local or remote resources. + // #[serde(default)] + // pub r#type: IndexKind, +} + +// #[derive( +// Default, Debug, Copy, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize, +// )] +// #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +// pub enum IndexKind { +// /// A PEP 503 and/or PEP 691-compliant index. +// #[default] +// Simple, +// /// An index containing a list of links to distributions (e.g., `--find-links`). +// Flat, +// } + +impl Index { + /// Initialize an [`Index`] from a pip-style `--index-url`. + pub fn from_index_url(url: IndexUrl) -> Self { + Self { + url, + name: None, + explicit: false, + default: true, + origin: None, + } + } + + /// Initialize an [`Index`] from a pip-style `--extra-index-url`. + pub fn from_extra_index_url(url: IndexUrl) -> Self { + Self { + url, + name: None, + explicit: false, + default: false, + origin: None, + } + } + + /// Initialize an [`Index`] from a pip-style `--find-links`. + pub fn from_find_links(url: IndexUrl) -> Self { + Self { + url, + name: None, + explicit: false, + default: false, + origin: None, + } + } + + /// Set the [`Origin`] of the index. + #[must_use] + pub fn with_origin(mut self, origin: Origin) -> Self { + self.origin = Some(origin); + self + } + + /// Return the [`IndexUrl`] of the index. + pub fn url(&self) -> &IndexUrl { + &self.url + } + + /// Consume the [`Index`] and return the [`IndexUrl`]. + pub fn into_url(self) -> IndexUrl { + self.url + } + + /// Return the raw [`URL`] of the index. + pub fn raw_url(&self) -> &Url { + self.url.url() + } + + /// Retrieve the credentials for the index, either from the environment, or from the URL itself. + pub fn credentials(&self) -> Option { + // If the index is named, and credentials are provided via the environment, prefer those. + if let Some(name) = self.name.as_deref() { + if let Some(credentials) = Credentials::from_env(name) { + return Some(credentials); + } + } + + // Otherwise, extract the credentials from the URL. + Credentials::from_url(self.url.url()) + } +} + +impl FromStr for Index { + type Err = IndexSourceError; + + fn from_str(s: &str) -> Result { + // Determine whether the source is prefixed with a name, as in `name=https://pypi.org/simple`. + if let Some((name, url)) = s.split_once('=') { + if name.is_empty() { + return Err(IndexSourceError::EmptyName); + } + + if name.chars().all(char::is_alphanumeric) { + let url = IndexUrl::from_str(url)?; + return Ok(Self { + name: Some(name.to_string()), + url, + explicit: false, + default: false, + origin: None, + }); + } + } + + // Otherwise, assume the source is a URL. + let url = IndexUrl::from_str(s)?; + Ok(Self { + name: None, + url, + explicit: false, + default: false, + origin: None, + }) + } +} + +/// An error that can occur when parsing an [`Index`]. +#[derive(Error, Debug)] +pub enum IndexSourceError { + #[error(transparent)] + Url(#[from] IndexUrlError), + #[error("Index included a name, but the name was empty")] + EmptyName, +} diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index cd3eb8ad1..c326a4c08 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -1,5 +1,5 @@ use itertools::Either; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::fmt::{Display, Formatter}; use std::ops::Deref; @@ -11,12 +11,13 @@ use url::{ParseError, Url}; use uv_pep508::{VerbatimUrl, VerbatimUrlError}; -use crate::Verbatim; +use crate::{Index, Verbatim}; static PYPI_URL: LazyLock = LazyLock::new(|| Url::parse("https://pypi.org/simple").unwrap()); -static DEFAULT_INDEX_URL: LazyLock = - LazyLock::new(|| IndexUrl::Pypi(VerbatimUrl::from_url(PYPI_URL.clone()))); +static DEFAULT_INDEX: LazyLock = LazyLock::new(|| { + Index::from_index_url(IndexUrl::Pypi(VerbatimUrl::from_url(PYPI_URL.clone()))) +}); /// The URL of an index to use for fetching packages (e.g., PyPI). #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -55,6 +56,15 @@ impl IndexUrl { } } + /// Convert the index URL into a [`Url`]. + pub fn into_url(self) -> Url { + match self { + Self::Pypi(url) => url.into_url(), + Self::Url(url) => url.into_url(), + Self::Path(url) => url.into_url(), + } + } + /// Return the redacted URL for the index, omitting any sensitive credentials. pub fn redacted(&self) -> Cow<'_, Url> { let url = self.url(); @@ -89,15 +99,6 @@ impl Verbatim for IndexUrl { } } -impl From for IndexUrl { - fn from(location: FlatIndexLocation) -> Self { - match location { - FlatIndexLocation::Path(url) => Self::Path(url), - FlatIndexLocation::Url(url) => Self::Url(url), - } - } -} - /// An error that can occur when parsing an [`IndexUrl`]. #[derive(Error, Debug)] pub enum IndexUrlError { @@ -175,152 +176,23 @@ impl Deref for IndexUrl { } } -/// A directory with distributions or a URL to an HTML file with a flat listing of distributions. -/// -/// Also known as `--find-links`. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum FlatIndexLocation { - Path(VerbatimUrl), - Url(VerbatimUrl), -} - -#[cfg(feature = "schemars")] -impl schemars::JsonSchema for FlatIndexLocation { - fn schema_name() -> String { - "FlatIndexLocation".to_string() - } - - fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - metadata: Some(Box::new(schemars::schema::Metadata { - description: Some("The path to a directory of distributions, or a URL to an HTML file with a flat listing of distributions.".to_string()), - ..schemars::schema::Metadata::default() - })), - ..schemars::schema::SchemaObject::default() - } - .into() - } -} - -impl FlatIndexLocation { - /// Return the raw URL for the `--find-links` index. - pub fn url(&self) -> &Url { - match self { - Self::Url(url) => url.raw(), - Self::Path(url) => url.raw(), - } - } - - /// Return the redacted URL for the `--find-links` index, omitting any sensitive credentials. - pub fn redacted(&self) -> Cow<'_, Url> { - let url = self.url(); - if url.username().is_empty() && url.password().is_none() { - Cow::Borrowed(url) - } else { - let mut url = url.clone(); - let _ = url.set_username(""); - let _ = url.set_password(None); - Cow::Owned(url) - } - } -} - -impl Display for FlatIndexLocation { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Url(url) => Display::fmt(url, f), - Self::Path(url) => Display::fmt(url, f), - } - } -} - -impl Verbatim for FlatIndexLocation { - fn verbatim(&self) -> Cow<'_, str> { - match self { - Self::Url(url) => url.verbatim(), - Self::Path(url) => url.verbatim(), - } - } -} - -impl FromStr for FlatIndexLocation { - type Err = IndexUrlError; - - fn from_str(s: &str) -> Result { - let url = if Path::new(s).exists() { - VerbatimUrl::from_absolute_path(std::path::absolute(s)?)? - } else { - VerbatimUrl::parse_url(s)? - }; - Ok(Self::from(url.with_given(s))) - } -} - -impl serde::ser::Serialize for FlatIndexLocation { - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - self.to_string().serialize(serializer) - } -} - -impl<'de> serde::de::Deserialize<'de> for FlatIndexLocation { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - FlatIndexLocation::from_str(&s).map_err(serde::de::Error::custom) - } -} - -impl From for FlatIndexLocation { - fn from(url: VerbatimUrl) -> Self { - if url.scheme() == "file" { - Self::Path(url) - } else { - Self::Url(url) - } - } -} - /// 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, serde::Serialize, serde::Deserialize)] +/// This type merges the legacy `--index-url`, `--extra-index-url`, and `--find-links` options, +/// along with the uv-specific `--index` and `--default-index`. +#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct IndexLocations { - index: Option, - extra_index: Vec, - flat_index: Vec, + indexes: Vec, + flat_index: Vec, no_index: bool, } -impl Default for IndexLocations { - /// By default, use the `PyPI` index. - fn default() -> Self { - Self { - index: Some(DEFAULT_INDEX_URL.clone()), - extra_index: Vec::new(), - flat_index: Vec::new(), - no_index: false, - } - } -} - impl IndexLocations { /// Determine the index URLs to use for fetching packages. - pub fn new( - index: Option, - extra_index: Vec, - flat_index: Vec, - no_index: bool, - ) -> Self { + pub fn new(indexes: Vec, flat_index: Vec, no_index: bool) -> Self { Self { - index, - extra_index, + indexes, flat_index, no_index, } @@ -333,16 +205,9 @@ impl IndexLocations { /// /// If the current index location has an `index` set, it will be preserved. #[must_use] - pub fn combine( - self, - index: Option, - extra_index: Vec, - flat_index: Vec, - no_index: bool, - ) -> Self { + pub fn combine(self, indexes: Vec, flat_index: Vec, no_index: bool) -> Self { Self { - index: self.index.or(index), - extra_index: self.extra_index.into_iter().chain(extra_index).collect(), + indexes: self.indexes.into_iter().chain(indexes).collect(), flat_index: self.flat_index.into_iter().chain(flat_index).collect(), no_index: self.no_index || no_index, } @@ -351,51 +216,73 @@ impl IndexLocations { /// Returns `true` if no index configuration is set, i.e., the [`IndexLocations`] matches the /// default configuration. pub fn is_none(&self) -> bool { - self.index.is_none() - && self.extra_index.is_empty() - && self.flat_index.is_empty() - && !self.no_index + *self == Self::default() } } impl<'a> IndexLocations { - /// Return the primary [`IndexUrl`] entry. + /// Return the default [`Index`] entry. /// /// If `--no-index` is set, return `None`. /// /// If no index is provided, use the `PyPI` index. - pub fn index(&'a self) -> Option<&'a IndexUrl> { + pub fn default_index(&'a self) -> Option<&'a Index> { if self.no_index { None } else { - match self.index.as_ref() { - Some(index) => Some(index), - None => Some(&DEFAULT_INDEX_URL), - } + let mut seen = FxHashSet::default(); + self.indexes + .iter() + .filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name))) + .find(|index| index.default && !index.explicit) + .or_else(|| Some(&DEFAULT_INDEX)) } } - /// Return an iterator over the extra [`IndexUrl`] entries. - pub fn extra_index(&'a self) -> impl Iterator + 'a { + /// Return an iterator over the implicit [`Index`] entries. + pub fn implicit_indexes(&'a self) -> impl Iterator + 'a { if self.no_index { Either::Left(std::iter::empty()) } else { - Either::Right(self.extra_index.iter()) + let mut seen = FxHashSet::default(); + Either::Right( + self.indexes + .iter() + .filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name))) + .filter(|index| !(index.default || index.explicit)), + ) } } - /// Return an iterator over all [`IndexUrl`] entries in order. + /// Return an iterator over the explicit [`Index`] entries. + pub fn explicit_indexes(&'a self) -> impl Iterator + 'a { + if self.no_index { + Either::Left(std::iter::empty()) + } else { + let mut seen = FxHashSet::default(); + Either::Right( + self.indexes + .iter() + .filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name))) + .filter(|index| index.explicit), + ) + } + } + + /// Return an iterator over all [`Index`] entries in order. /// - /// Prioritizes the extra indexes over the main index. + /// Explicit indexes are excluded. + /// + /// Prioritizes the extra indexes over the default index. /// /// If `no_index` was enabled, then this always returns an empty /// iterator. - pub fn indexes(&'a self) -> impl Iterator + 'a { - self.extra_index().chain(self.index()) + pub fn indexes(&'a self) -> impl Iterator + 'a { + self.implicit_indexes().chain(self.default_index()) } /// Return an iterator over the [`FlatIndexLocation`] entries. - pub fn flat_index(&'a self) -> impl Iterator + 'a { + pub fn flat_indexes(&'a self) -> impl Iterator + 'a { self.flat_index.iter() } @@ -407,111 +294,181 @@ impl<'a> IndexLocations { /// Clone the index locations into a [`IndexUrls`] instance. pub fn index_urls(&'a self) -> IndexUrls { IndexUrls { - index: self.index.clone(), - extra_index: self.extra_index.clone(), + indexes: self.indexes.clone(), no_index: self.no_index, } } - /// Return an iterator over all [`Url`] entries. - pub fn urls(&'a self) -> impl Iterator + 'a { - self.indexes() - .map(IndexUrl::url) - .chain(self.flat_index.iter().filter_map(|index| match index { - FlatIndexLocation::Path(_) => None, - FlatIndexLocation::Url(url) => Some(url.raw()), - })) + /// Return a vector containing all allowed [`Index`] entries. + /// + /// This includes explicit indexes, implicit indexes, flat indexes, and the default index. + /// + /// The indexes will be returned in the order in which they were defined, such that the + /// last-defined index is the last item in the vector. + pub fn allowed_indexes(&'a self) -> Vec<&'a Index> { + if self.no_index { + self.flat_index.iter().rev().collect() + } else { + let mut indexes = vec![]; + + let mut seen = FxHashSet::default(); + let mut default = false; + for index in { + self.indexes + .iter() + .chain(self.flat_index.iter()) + .filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name))) + } { + if index.default && !index.explicit { + if default { + continue; + } + default = true; + } + indexes.push(index); + } + if !default { + indexes.push(&*DEFAULT_INDEX); + } + + indexes.reverse(); + indexes + } } } /// The index URLs to use for fetching packages. /// -/// From a pip perspective, this type merges `--index-url` and `--extra-index-url`. -#[derive(Debug, Clone)] +/// This type merges the legacy `--index-url` and `--extra-index-url` options, along with the +/// uv-specific `--index` and `--default-index`. +#[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct IndexUrls { - index: Option, - extra_index: Vec, + indexes: Vec, no_index: bool, } -impl Default for IndexUrls { - /// By default, use the `PyPI` index. - fn default() -> Self { - Self { - index: Some(DEFAULT_INDEX_URL.clone()), - extra_index: Vec::new(), - no_index: false, - } - } -} - impl<'a> IndexUrls { - /// Return the fallback [`IndexUrl`] entry. + /// Return the default [`Index`] entry. /// /// If `--no-index` is set, return `None`. /// /// If no index is provided, use the `PyPI` index. - fn index(&'a self) -> Option<&'a IndexUrl> { + fn default_index(&'a self) -> Option<&'a Index> { if self.no_index { None } else { - match self.index.as_ref() { - Some(index) => Some(index), - None => Some(&DEFAULT_INDEX_URL), - } + let mut seen = FxHashSet::default(); + self.indexes + .iter() + .filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name))) + .find(|index| index.default && !index.explicit) + .or_else(|| Some(&DEFAULT_INDEX)) } } - /// Return an iterator over the extra [`IndexUrl`] entries. - fn extra_index(&'a self) -> impl Iterator + 'a { + /// Return an iterator over the implicit [`Index`] entries. + fn implicit_indexes(&'a self) -> impl Iterator + 'a { if self.no_index { Either::Left(std::iter::empty()) } else { - Either::Right(self.extra_index.iter()) + let mut seen = FxHashSet::default(); + Either::Right( + self.indexes + .iter() + .filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name))) + .filter(|index| !(index.default || index.explicit)), + ) } } /// Return an iterator over all [`IndexUrl`] entries in order. /// - /// Prioritizes the extra indexes over the main index. + /// Prioritizes the `[tool.uv.index]` definitions over the `--extra-index-url` definitions + /// over the `--index-url` definition. /// /// If `no_index` was enabled, then this always returns an empty /// iterator. - pub fn indexes(&'a self) -> impl Iterator + 'a { - self.extra_index().chain(self.index()) + pub fn indexes(&'a self) -> impl Iterator + 'a { + self.implicit_indexes().chain(self.default_index()) } } -impl From for IndexUrls { - fn from(locations: IndexLocations) -> Self { - Self { - index: locations.index, - extra_index: locations.extra_index, - no_index: locations.no_index, - } +bitflags::bitflags! { + #[derive(Debug, Copy, Clone)] + struct Flags: u8 { + /// Whether the index supports range requests. + const NO_RANGE_REQUESTS = 1; + /// Whether the index returned a `401 Unauthorized` status code. + const UNAUTHORIZED = 1 << 2; + /// Whether the index returned a `403 Forbidden` status code. + const FORBIDDEN = 1 << 1; } } /// A map of [`IndexUrl`]s to their capabilities. /// -/// For now, we only support a single capability (range requests), and we only store an index if -/// it _doesn't_ support range requests. The benefit is that the map is almost always empty, so -/// validating capabilities is extremely cheap. +/// We only store indexes that lack capabilities (i.e., don't support range requests, aren't +/// authorized). The benefit is that the map is almost always empty, so validating capabilities is +/// extremely cheap. #[derive(Debug, Default, Clone)] -pub struct IndexCapabilities(Arc>>); +pub struct IndexCapabilities(Arc>>); impl IndexCapabilities { /// Returns `true` if the given [`IndexUrl`] supports range requests. pub fn supports_range_requests(&self, index_url: &IndexUrl) -> bool { - !self.0.read().unwrap().contains(index_url) + !self + .0 + .read() + .unwrap() + .get(index_url) + .is_some_and(|flags| flags.intersects(Flags::NO_RANGE_REQUESTS)) } /// Mark an [`IndexUrl`] as not supporting range requests. - pub fn set_supports_range_requests(&self, index_url: IndexUrl, supports: bool) { - if supports { - self.0.write().unwrap().remove(&index_url); - } else { - self.0.write().unwrap().insert(index_url); - } + pub fn set_no_range_requests(&self, index_url: IndexUrl) { + self.0 + .write() + .unwrap() + .entry(index_url) + .or_insert(Flags::empty()) + .insert(Flags::NO_RANGE_REQUESTS); + } + + /// Returns `true` if the given [`IndexUrl`] returns a `401 Unauthorized` status code. + pub fn unauthorized(&self, index_url: &IndexUrl) -> bool { + self.0 + .read() + .unwrap() + .get(index_url) + .is_some_and(|flags| flags.intersects(Flags::UNAUTHORIZED)) + } + + /// Mark an [`IndexUrl`] as returning a `401 Unauthorized` status code. + pub fn set_unauthorized(&self, index_url: IndexUrl) { + self.0 + .write() + .unwrap() + .entry(index_url) + .or_insert(Flags::empty()) + .insert(Flags::UNAUTHORIZED); + } + + /// Returns `true` if the given [`IndexUrl`] returns a `403 Forbidden` status code. + pub fn forbidden(&self, index_url: &IndexUrl) -> bool { + self.0 + .read() + .unwrap() + .get(index_url) + .is_some_and(|flags| flags.intersects(Flags::FORBIDDEN)) + } + + /// Mark an [`IndexUrl`] as returning a `403 Forbidden` status code. + pub fn set_forbidden(&self, index_url: IndexUrl) { + self.0 + .write() + .unwrap() + .entry(index_url) + .or_insert(Flags::empty()) + .insert(Flags::FORBIDDEN); } } diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index a77d05185..b8c050baf 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -57,8 +57,11 @@ pub use crate::error::*; pub use crate::file::*; pub use crate::hash::*; pub use crate::id::*; +pub use crate::index::*; pub use crate::index_url::*; pub use crate::installed::*; +pub use crate::origin::*; +pub use crate::pip_index::*; pub use crate::prioritized_distribution::*; pub use crate::resolution::*; pub use crate::resolved::*; @@ -75,8 +78,11 @@ mod error; mod file; mod hash; mod id; +mod index; mod index_url; mod installed; +mod origin; +mod pip_index; mod prioritized_distribution; mod resolution; mod resolved; diff --git a/crates/uv-distribution-types/src/origin.rs b/crates/uv-distribution-types/src/origin.rs new file mode 100644 index 000000000..286735545 --- /dev/null +++ b/crates/uv-distribution-types/src/origin.rs @@ -0,0 +1,12 @@ +/// The origin of a piece of configuration. +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +pub enum Origin { + /// The setting was provided via the CLI. + Cli, + /// The setting was provided via a user-level configuration file. + User, + /// The setting was provided via a project-level configuration file. + Project, + /// The setting was provided via a `requirements.txt` file. + RequirementsTxt, +} diff --git a/crates/uv-distribution-types/src/pip_index.rs b/crates/uv-distribution-types/src/pip_index.rs new file mode 100644 index 000000000..9d5de50ea --- /dev/null +++ b/crates/uv-distribution-types/src/pip_index.rs @@ -0,0 +1,59 @@ +//! Compatibility structs for converting between [`IndexUrl`] and [`Index`]. These structs are +//! parsed and deserialized as [`IndexUrl`], but are stored as [`Index`] with the appropriate +//! flags set. + +use serde::{Deserialize, Deserializer, Serialize}; + +use crate::{Index, IndexUrl}; + +macro_rules! impl_index { + ($name:ident, $from:expr) => { + #[derive(Debug, Clone, Eq, PartialEq)] + pub struct $name(Index); + + impl From<$name> for Index { + fn from(value: $name) -> Self { + value.0 + } + } + + impl From for $name { + fn from(value: Index) -> Self { + Self(value) + } + } + + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.url().serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result<$name, D::Error> + where + D: Deserializer<'de>, + { + IndexUrl::deserialize(deserializer).map($from).map(Self) + } + } + + #[cfg(feature = "schemars")] + impl schemars::JsonSchema for $name { + fn schema_name() -> String { + IndexUrl::schema_name() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + IndexUrl::json_schema(gen) + } + } + }; +} + +impl_index!(PipIndex, Index::from_index_url); +impl_index!(PipExtraIndex, Index::from_extra_index_url); +impl_index!(PipFindLinks, Index::from_find_links); diff --git a/crates/uv-distribution-types/src/resolution.rs b/crates/uv-distribution-types/src/resolution.rs index f8b51b160..6ec6edce3 100644 --- a/crates/uv-distribution-types/src/resolution.rs +++ b/crates/uv-distribution-types/src/resolution.rs @@ -190,7 +190,7 @@ impl From<&ResolvedDist> for Requirement { wheels.best_wheel().filename.version.clone(), ), ), - index: None, + index: Some(wheels.best_wheel().index.url().clone()), }, Dist::Built(BuiltDist::DirectUrl(wheel)) => { let mut location = wheel.url.to_url(); @@ -211,7 +211,7 @@ impl From<&ResolvedDist> for Requirement { specifier: uv_pep440::VersionSpecifiers::from( uv_pep440::VersionSpecifier::equals_version(sdist.version.clone()), ), - index: None, + index: Some(sdist.index.url().clone()), }, Dist::Source(SourceDist::DirectUrl(sdist)) => { let mut location = sdist.url.to_url(); diff --git a/crates/uv-distribution/Cargo.toml b/crates/uv-distribution/Cargo.toml index 631066b51..eebbc1dd5 100644 --- a/crates/uv-distribution/Cargo.toml +++ b/crates/uv-distribution/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-distribution/src/index/registry_wheel_index.rs b/crates/uv-distribution/src/index/registry_wheel_index.rs index f751a3fea..a3530da1b 100644 --- a/crates/uv-distribution/src/index/registry_wheel_index.rs +++ b/crates/uv-distribution/src/index/registry_wheel_index.rs @@ -1,9 +1,9 @@ use std::collections::hash_map::Entry; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use uv_cache::{Cache, CacheBucket, WheelCache}; -use uv_distribution_types::{CachedRegistryDist, Hashed, IndexLocations, IndexUrl}; +use uv_distribution_types::{CachedRegistryDist, Hashed, Index, IndexLocations, IndexUrl}; use uv_fs::{directories, files, symlinks}; use uv_normalize::PackageName; use uv_platform_tags::Tags; @@ -14,11 +14,13 @@ use crate::source::{HttpRevisionPointer, LocalRevisionPointer, HTTP_REVISION, LO /// An entry in the [`RegistryWheelIndex`]. #[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct IndexEntry { +pub struct IndexEntry<'index> { /// The cached distribution. pub dist: CachedRegistryDist, /// Whether the wheel was built from source (true), or downloaded from the registry directly (false). pub built: bool, + /// The index from which the wheel was downloaded. + pub index: &'index Index, } /// A local index of distributions that originate from a registry, like `PyPI`. @@ -28,7 +30,7 @@ pub struct RegistryWheelIndex<'a> { tags: &'a Tags, index_locations: &'a IndexLocations, hasher: &'a HashStrategy, - index: FxHashMap<&'a PackageName, Vec>, + index: FxHashMap<&'a PackageName, Vec>>, } impl<'a> RegistryWheelIndex<'a> { @@ -71,32 +73,31 @@ impl<'a> RegistryWheelIndex<'a> { } /// Add a package to the index by reading from the cache. - fn index( + fn index<'index>( package: &PackageName, cache: &Cache, tags: &Tags, - index_locations: &IndexLocations, + index_locations: &'index IndexLocations, hasher: &HashStrategy, - ) -> Vec { + ) -> Vec> { let mut entries = vec![]; - // Collect into owned `IndexUrl`. - let flat_index_urls: Vec = index_locations - .flat_index() - .map(|flat_index| IndexUrl::from(flat_index.clone())) - .collect(); + let mut seen = FxHashSet::default(); + for index in index_locations.allowed_indexes() { + if !seen.insert(index.url()) { + continue; + } - for index_url in index_locations.indexes().chain(flat_index_urls.iter()) { // Index all the wheels that were downloaded directly from the registry. let wheel_dir = cache.shard( CacheBucket::Wheels, - WheelCache::Index(index_url).wheel_dir(package.to_string()), + WheelCache::Index(index.url()).wheel_dir(package.to_string()), ); // For registry wheels, the cache structure is: `//.http` // or `///.rev`. for file in files(&wheel_dir) { - match index_url { + match index.url() { // Add files from remote registries. IndexUrl::Pypi(_) | IndexUrl::Url(_) => { if file @@ -116,6 +117,7 @@ impl<'a> RegistryWheelIndex<'a> { ) { entries.push(IndexEntry { dist: wheel.into_registry_dist(), + index, built: false, }); } @@ -142,6 +144,7 @@ impl<'a> RegistryWheelIndex<'a> { ) { entries.push(IndexEntry { dist: wheel.into_registry_dist(), + index, built: false, }); } @@ -156,7 +159,7 @@ impl<'a> RegistryWheelIndex<'a> { // from the registry. let cache_shard = cache.shard( CacheBucket::SourceDistributions, - WheelCache::Index(index_url).wheel_dir(package.to_string()), + WheelCache::Index(index.url()).wheel_dir(package.to_string()), ); // For registry wheels, the cache structure is: `///`. @@ -165,7 +168,7 @@ impl<'a> RegistryWheelIndex<'a> { let cache_shard = cache_shard.shard(shard); // Read the revision from the cache. - let revision = match index_url { + let revision = match index.url() { // Add files from remote registries. IndexUrl::Pypi(_) | IndexUrl::Url(_) => { let revision_entry = cache_shard.entry(HTTP_REVISION); @@ -197,6 +200,7 @@ impl<'a> RegistryWheelIndex<'a> { ) { entries.push(IndexEntry { dist: wheel.into_registry_dist(), + index, built: true, }); } diff --git a/crates/uv-distribution/src/lib.rs b/crates/uv-distribution/src/lib.rs index 7a15a44c3..13ff9a1d6 100644 --- a/crates/uv-distribution/src/lib.rs +++ b/crates/uv-distribution/src/lib.rs @@ -2,7 +2,7 @@ pub use distribution_database::{DistributionDatabase, HttpArchivePointer, LocalA pub use download::LocalWheel; pub use error::Error; pub use index::{BuiltWheelIndex, RegistryWheelIndex}; -pub use metadata::{ArchiveMetadata, LoweredRequirement, Metadata, RequiresDist}; +pub use metadata::{ArchiveMetadata, LoweredRequirement, Metadata, MetadataError, RequiresDist}; pub use reporter::Reporter; pub use source::prune; diff --git a/crates/uv-distribution/src/metadata/lowering.rs b/crates/uv-distribution/src/metadata/lowering.rs index 8a1716693..2efa90309 100644 --- a/crates/uv-distribution/src/metadata/lowering.rs +++ b/crates/uv-distribution/src/metadata/lowering.rs @@ -4,8 +4,9 @@ use std::io; use std::path::{Path, PathBuf}; use thiserror::Error; use url::Url; - +use uv_configuration::LowerBound; use uv_distribution_filename::DistExtension; +use uv_distribution_types::{Index, IndexLocations, Origin}; use uv_git::GitReference; use uv_normalize::PackageName; use uv_pep440::VersionSpecifiers; @@ -19,7 +20,7 @@ use uv_workspace::Workspace; pub struct LoweredRequirement(Requirement); #[derive(Debug, Clone, Copy)] -enum Origin { +enum RequirementOrigin { /// The `tool.uv.sources` were read from the project. Project, /// The `tool.uv.sources` were read from the workspace root. @@ -33,14 +34,17 @@ impl LoweredRequirement { project_name: &'data PackageName, project_dir: &'data Path, project_sources: &'data BTreeMap, + project_indexes: &'data [Index], + locations: &'data IndexLocations, workspace: &'data Workspace, - ) -> impl Iterator> + 'data { + lower_bound: LowerBound, + ) -> impl Iterator> + 'data { let (source, origin) = if let Some(source) = project_sources.get(&requirement.name) { - (Some(source), Origin::Project) + (Some(source), RequirementOrigin::Project) } else if let Some(source) = workspace.sources().get(&requirement.name) { - (Some(source), Origin::Workspace) + (Some(source), RequirementOrigin::Workspace) } else { - (None, Origin::Project) + (None, RequirementOrigin::Project) }; let source = source.cloned(); @@ -62,15 +66,17 @@ impl LoweredRequirement { let Some(source) = source else { let has_sources = !project_sources.is_empty() || !workspace.sources().is_empty(); - // Support recursive editable inclusions. - if has_sources - && requirement.version_or_url.is_none() - && &requirement.name != project_name - { - warn_user_once!( - "Missing version constraint (e.g., a lower bound) for `{}`", - requirement.name - ); + if matches!(lower_bound, LowerBound::Warn) { + // Support recursive editable inclusions. + if has_sources + && requirement.version_or_url.is_none() + && &requirement.name != project_name + { + warn_user_once!( + "Missing version constraint (e.g., a lower bound) for `{}`", + requirement.name + ); + } } return Either::Left(std::iter::once(Ok(Self(Requirement::from(requirement))))); }; @@ -148,7 +154,25 @@ impl LoweredRequirement { (source, marker) } Source::Registry { index, marker } => { - let source = registry_source(&requirement, index)?; + // Identify the named index from either the project indexes or the workspace indexes, + // in that order. + let Some(index) = locations + .indexes() + .filter(|index| matches!(index.origin, Some(Origin::Cli))) + .chain(project_indexes.iter()) + .chain(workspace.indexes().iter()) + .find(|Index { name, .. }| { + name.as_ref().is_some_and(|name| *name == index) + }) + .map(|Index { url: index, .. }| index.clone()) + else { + return Err(LoweringError::MissingIndex( + requirement.name.clone(), + index, + )); + }; + let source = + registry_source(&requirement, index.into_url(), lower_bound)?; (source, marker) } Source::Workspace { @@ -235,7 +259,10 @@ impl LoweredRequirement { requirement: uv_pep508::Requirement, dir: &'data Path, sources: &'data BTreeMap, - ) -> impl Iterator> + 'data { + indexes: &'data [Index], + locations: &'data IndexLocations, + lower_bound: LowerBound, + ) -> impl Iterator> + 'data { let source = sources.get(&requirement.name).cloned(); let Some(source) = source else { @@ -307,7 +334,7 @@ impl LoweredRequirement { } let source = path_source( PathBuf::from(path), - Origin::Project, + RequirementOrigin::Project, dir, dir, editable.unwrap_or(false), @@ -315,7 +342,22 @@ impl LoweredRequirement { (source, marker) } Source::Registry { index, marker } => { - let source = registry_source(&requirement, index)?; + let Some(index) = locations + .indexes() + .filter(|index| matches!(index.origin, Some(Origin::Cli))) + .chain(indexes.iter()) + .find(|Index { name, .. }| { + name.as_ref().is_some_and(|name| *name == index) + }) + .map(|Index { url: index, .. }| index.clone()) + else { + return Err(LoweringError::MissingIndex( + requirement.name.clone(), + index, + )); + }; + let source = + registry_source(&requirement, index.into_url(), lower_bound)?; (source, marker) } Source::Workspace { .. } => { @@ -355,6 +397,8 @@ pub enum LoweringError { UndeclaredWorkspacePackage, #[error("Can only specify one of: `rev`, `tag`, or `branch`")] MoreThanOneGitRef, + #[error("Package `{0}` references an undeclared index: `{1}`")] + MissingIndex(PackageName, String), #[error("Workspace members are not allowed in non-workspace contexts")] WorkspaceMember, #[error(transparent)] @@ -445,14 +489,17 @@ fn url_source(url: Url, subdirectory: Option) -> Result, - index: String, + index: Url, + bounds: LowerBound, ) -> Result { match &requirement.version_or_url { None => { - warn_user_once!( - "Missing version constraint (e.g., a lower bound) for `{}`", - requirement.name - ); + if matches!(bounds, LowerBound::Warn) { + warn_user_once!( + "Missing version constraint (e.g., a lower bound) for `{}`", + requirement.name + ); + } Ok(RequirementSource::Registry { specifier: VersionSpecifiers::empty(), index: Some(index), @@ -469,15 +516,15 @@ fn registry_source( /// Convert a path string to a file or directory source. fn path_source( path: impl AsRef, - origin: Origin, + origin: RequirementOrigin, project_dir: &Path, workspace_root: &Path, editable: bool, ) -> Result { let path = path.as_ref(); let base = match origin { - Origin::Project => project_dir, - Origin::Workspace => workspace_root, + RequirementOrigin::Project => project_dir, + RequirementOrigin::Workspace => workspace_root, }; let url = VerbatimUrl::from_path(path, base)?.with_given(path.to_string_lossy()); let install_path = url.to_file_path().map_err(|()| { diff --git a/crates/uv-distribution/src/metadata/mod.rs b/crates/uv-distribution/src/metadata/mod.rs index 6f18baa0e..141f777bf 100644 --- a/crates/uv-distribution/src/metadata/mod.rs +++ b/crates/uv-distribution/src/metadata/mod.rs @@ -3,7 +3,8 @@ use std::path::Path; use thiserror::Error; -use uv_configuration::SourceStrategy; +use uv_configuration::{LowerBound, SourceStrategy}; +use uv_distribution_types::IndexLocations; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep440::{Version, VersionSpecifiers}; use uv_pypi_types::{HashDigest, ResolutionMetadata}; @@ -61,7 +62,9 @@ impl Metadata { pub async fn from_workspace( metadata: ResolutionMetadata, install_path: &Path, + locations: &IndexLocations, sources: SourceStrategy, + bounds: LowerBound, ) -> Result { // Lower the requirements. let RequiresDist { @@ -76,7 +79,9 @@ impl Metadata { provides_extras: metadata.provides_extras, }, install_path, + locations, sources, + bounds, ) .await?; diff --git a/crates/uv-distribution/src/metadata/requires_dist.rs b/crates/uv-distribution/src/metadata/requires_dist.rs index 7c3564b54..e1b1d763d 100644 --- a/crates/uv-distribution/src/metadata/requires_dist.rs +++ b/crates/uv-distribution/src/metadata/requires_dist.rs @@ -3,7 +3,8 @@ use crate::Metadata; use std::collections::BTreeMap; use std::path::Path; -use uv_configuration::SourceStrategy; +use uv_configuration::{LowerBound, SourceStrategy}; +use uv_distribution_types::IndexLocations; use uv_normalize::{ExtraName, GroupName, PackageName, DEV_DEPENDENCIES}; use uv_workspace::pyproject::ToolUvSources; use uv_workspace::{DiscoveryOptions, ProjectWorkspace}; @@ -37,7 +38,9 @@ impl RequiresDist { pub async fn from_project_maybe_workspace( metadata: uv_pypi_types::RequiresDist, install_path: &Path, + locations: &IndexLocations, sources: SourceStrategy, + lower_bound: LowerBound, ) -> Result { // TODO(konsti): Limit discovery for Git checkouts to Git root. // TODO(konsti): Cache workspace discovery. @@ -48,17 +51,39 @@ impl RequiresDist { return Ok(Self::from_metadata23(metadata)); }; - Self::from_project_workspace(metadata, &project_workspace, sources) + Self::from_project_workspace( + metadata, + &project_workspace, + locations, + sources, + lower_bound, + ) } fn from_project_workspace( metadata: uv_pypi_types::RequiresDist, project_workspace: &ProjectWorkspace, + locations: &IndexLocations, source_strategy: SourceStrategy, + lower_bound: LowerBound, ) -> Result { + // Collect any `tool.uv.index` entries. + let empty = vec![]; + let project_indexes = match source_strategy { + SourceStrategy::Enabled => project_workspace + .current_project() + .pyproject_toml() + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.index.as_deref()) + .unwrap_or(&empty), + SourceStrategy::Disabled => &empty, + }; + // Collect any `tool.uv.sources` and `tool.uv.dev_dependencies` from `pyproject.toml`. let empty = BTreeMap::default(); - let sources = match source_strategy { + let project_sources = match source_strategy { SourceStrategy::Enabled => project_workspace .current_project() .pyproject_toml() @@ -90,8 +115,11 @@ impl RequiresDist { requirement, &metadata.name, project_workspace.project_root(), - sources, + project_sources, + project_indexes, + locations, project_workspace.workspace(), + lower_bound, ) .map(move |requirement| match requirement { Ok(requirement) => Ok(requirement.into_inner()), @@ -122,8 +150,11 @@ impl RequiresDist { requirement, &metadata.name, project_workspace.project_root(), - sources, + project_sources, + project_indexes, + locations, project_workspace.workspace(), + lower_bound, ) .map(move |requirement| match requirement { Ok(requirement) => Ok(requirement.into_inner()), @@ -166,7 +197,8 @@ mod test { use anyhow::Context; use indoc::indoc; use insta::assert_snapshot; - use uv_configuration::SourceStrategy; + use uv_configuration::{LowerBound, SourceStrategy}; + use uv_distribution_types::IndexLocations; use uv_workspace::pyproject::PyProjectToml; use uv_workspace::{DiscoveryOptions, ProjectWorkspace}; @@ -192,7 +224,9 @@ mod test { Ok(RequiresDist::from_project_workspace( requires_dist, &project_workspace, - SourceStrategy::Enabled, + &IndexLocations::default(), + SourceStrategy::default(), + LowerBound::default(), )?) } diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 72e83a37d..a8e3d0113 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -5,13 +5,6 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; -use crate::distribution_database::ManagedClient; -use crate::error::Error; -use crate::metadata::{ArchiveMetadata, Metadata}; -use crate::reporter::Facade; -use crate::source::built_wheel_metadata::BuiltWheelMetadata; -use crate::source::revision::Revision; -use crate::{Reporter, RequiresDist}; use fs_err::tokio as fs; use futures::{FutureExt, TryStreamExt}; use reqwest::Response; @@ -24,7 +17,7 @@ use uv_cache_key::cache_digest; use uv_client::{ CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient, }; -use uv_configuration::{BuildKind, BuildOutput}; +use uv_configuration::{BuildKind, BuildOutput, SourceStrategy}; use uv_distribution_filename::{SourceDistExtension, WheelFilename}; use uv_distribution_types::{ BuildableSource, DirectorySourceUrl, FileLocation, GitSourceUrl, HashPolicy, Hashed, @@ -38,6 +31,14 @@ use uv_pypi_types::{HashDigest, Metadata12, RequiresTxt, ResolutionMetadata}; use uv_types::{BuildContext, SourceBuildTrait}; use zip::ZipArchive; +use crate::distribution_database::ManagedClient; +use crate::error::Error; +use crate::metadata::{ArchiveMetadata, Metadata}; +use crate::reporter::Facade; +use crate::source::built_wheel_metadata::BuiltWheelMetadata; +use crate::source::revision::Revision; +use crate::{Reporter, RequiresDist}; + mod built_wheel_metadata; mod revision; @@ -388,7 +389,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let requires_dist = RequiresDist::from_project_maybe_workspace( requires_dist, project_root, + self.build_context.locations(), self.build_context.sources(), + self.build_context.bounds(), ) .await?; Ok(requires_dist) @@ -465,7 +468,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Build the source distribution. let (disk_filename, wheel_filename, metadata) = self - .build_distribution(source, source_dist_entry.path(), subdirectory, &cache_shard) + .build_distribution( + source, + source_dist_entry.path(), + subdirectory, + &cache_shard, + SourceStrategy::Disabled, + ) .await?; if let Some(task) = task { @@ -573,7 +582,12 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Otherwise, we either need to build the metadata. // If the backend supports `prepare_metadata_for_build_wheel`, use it. if let Some(metadata) = self - .build_metadata(source, source_dist_entry.path(), subdirectory) + .build_metadata( + source, + source_dist_entry.path(), + subdirectory, + SourceStrategy::Disabled, + ) .boxed_local() .await? { @@ -598,7 +612,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Build the source distribution. let (_disk_filename, _wheel_filename, metadata) = self - .build_distribution(source, source_dist_entry.path(), subdirectory, &cache_shard) + .build_distribution( + source, + source_dist_entry.path(), + subdirectory, + &cache_shard, + SourceStrategy::Disabled, + ) .await?; // Store the metadata. @@ -750,7 +770,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map(|reporter| reporter.on_build_start(source)); let (disk_filename, filename, metadata) = self - .build_distribution(source, source_entry.path(), None, &cache_shard) + .build_distribution( + source, + source_entry.path(), + None, + &cache_shard, + SourceStrategy::Disabled, + ) .await?; if let Some(task) = task { @@ -836,7 +862,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // If the backend supports `prepare_metadata_for_build_wheel`, use it. if let Some(metadata) = self - .build_metadata(source, source_entry.path(), None) + .build_metadata(source, source_entry.path(), None, SourceStrategy::Disabled) .boxed_local() .await? { @@ -869,7 +895,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map(|reporter| reporter.on_build_start(source)); let (_disk_filename, _filename, metadata) = self - .build_distribution(source, source_entry.path(), None, &cache_shard) + .build_distribution( + source, + source_entry.path(), + None, + &cache_shard, + SourceStrategy::Disabled, + ) .await?; if let Some(task) = task { @@ -998,7 +1030,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map(|reporter| reporter.on_build_start(source)); let (disk_filename, filename, metadata) = self - .build_distribution(source, &resource.install_path, None, &cache_shard) + .build_distribution( + source, + &resource.install_path, + None, + &cache_shard, + self.build_context.sources(), + ) .await?; if let Some(task) = task { @@ -1045,7 +1083,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Metadata::from_workspace( metadata, resource.install_path.as_ref(), + self.build_context.locations(), self.build_context.sources(), + self.build_context.bounds(), ) .await?, )); @@ -1079,7 +1119,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Metadata::from_workspace( metadata, resource.install_path.as_ref(), + self.build_context.locations(), self.build_context.sources(), + self.build_context.bounds(), ) .await?, )); @@ -1087,7 +1129,12 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // If the backend supports `prepare_metadata_for_build_wheel`, use it. if let Some(metadata) = self - .build_metadata(source, &resource.install_path, None) + .build_metadata( + source, + &resource.install_path, + None, + self.build_context.sources(), + ) .boxed_local() .await? { @@ -1103,7 +1150,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Metadata::from_workspace( metadata, resource.install_path.as_ref(), + self.build_context.locations(), self.build_context.sources(), + self.build_context.bounds(), ) .await?, )); @@ -1124,7 +1173,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map(|reporter| reporter.on_build_start(source)); let (_disk_filename, _filename, metadata) = self - .build_distribution(source, &resource.install_path, None, &cache_shard) + .build_distribution( + source, + &resource.install_path, + None, + &cache_shard, + self.build_context.sources(), + ) .await?; if let Some(task) = task { @@ -1142,7 +1197,9 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Metadata::from_workspace( metadata, resource.install_path.as_ref(), + self.build_context.locations(), self.build_context.sources(), + self.build_context.bounds(), ) .await?, )) @@ -1246,7 +1303,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map(|reporter| reporter.on_build_start(source)); let (disk_filename, filename, metadata) = self - .build_distribution(source, fetch.path(), resource.subdirectory, &cache_shard) + .build_distribution( + source, + fetch.path(), + resource.subdirectory, + &cache_shard, + self.build_context.sources(), + ) .await?; if let Some(task) = task { @@ -1316,7 +1379,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { Self::read_static_metadata(source, fetch.path(), resource.subdirectory).await? { return Ok(ArchiveMetadata::from( - Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?, + Metadata::from_workspace( + metadata, + &path, + self.build_context.locations(), + self.build_context.sources(), + self.build_context.bounds(), + ) + .await?, )); } @@ -1337,14 +1407,26 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { debug!("Using cached metadata for: {source}"); return Ok(ArchiveMetadata::from( - Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?, + Metadata::from_workspace( + metadata, + &path, + self.build_context.locations(), + self.build_context.sources(), + self.build_context.bounds(), + ) + .await?, )); } } // If the backend supports `prepare_metadata_for_build_wheel`, use it. if let Some(metadata) = self - .build_metadata(source, fetch.path(), resource.subdirectory) + .build_metadata( + source, + fetch.path(), + resource.subdirectory, + self.build_context.sources(), + ) .boxed_local() .await? { @@ -1357,7 +1439,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map_err(Error::CacheWrite)?; return Ok(ArchiveMetadata::from( - Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?, + Metadata::from_workspace( + metadata, + &path, + self.build_context.locations(), + self.build_context.sources(), + self.build_context.bounds(), + ) + .await?, )); } @@ -1376,7 +1465,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map(|reporter| reporter.on_build_start(source)); let (_disk_filename, _filename, metadata) = self - .build_distribution(source, fetch.path(), resource.subdirectory, &cache_shard) + .build_distribution( + source, + fetch.path(), + resource.subdirectory, + &cache_shard, + self.build_context.sources(), + ) .await?; if let Some(task) = task { @@ -1391,7 +1486,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .map_err(Error::CacheWrite)?; Ok(ArchiveMetadata::from( - Metadata::from_workspace(metadata, fetch.path(), self.build_context.sources()).await?, + Metadata::from_workspace( + metadata, + fetch.path(), + self.build_context.locations(), + self.build_context.sources(), + self.build_context.bounds(), + ) + .await?, )) } @@ -1584,6 +1686,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source_root: &Path, subdirectory: Option<&Path>, cache_shard: &CacheShard, + source_strategy: SourceStrategy, ) -> Result<(String, WheelFilename, ResolutionMetadata), Error> { debug!("Building: {source}"); @@ -1609,8 +1712,10 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .setup_build( source_root, subdirectory, + source_root, Some(source.to_string()), source.as_dist(), + source_strategy, if source.is_editable() { BuildKind::Editable } else { @@ -1642,6 +1747,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source: &BuildableSource<'_>, source_root: &Path, subdirectory: Option<&Path>, + source_strategy: SourceStrategy, ) -> Result, Error> { debug!("Preparing metadata for: {source}"); @@ -1651,8 +1757,10 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .setup_build( source_root, subdirectory, + source_root, Some(source.to_string()), source.as_dist(), + source_strategy, if source.is_editable() { BuildKind::Editable } else { diff --git a/crates/uv-extract/Cargo.toml b/crates/uv-extract/Cargo.toml index 00f084f21..1ebc4edc6 100644 --- a/crates/uv-extract/Cargo.toml +++ b/crates/uv-extract/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-fs/Cargo.toml b/crates/uv-fs/Cargo.toml index c7aa1cb1a..1f5627f6f 100644 --- a/crates/uv-fs/Cargo.toml +++ b/crates/uv-fs/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -29,6 +32,12 @@ tempfile = { workspace = true } tracing = { workspace = true } urlencoding = { workspace = true } +[target.'cfg(target_os = "windows")'.dependencies] +winsafe = { workspace = true } + +[target.'cfg(any(unix, target_os = "wasi", target_os = "redox"))'.dependencies] +rustix = { workspace = true } + [target.'cfg(windows)'.dependencies] junction = { workspace = true } diff --git a/crates/uv-fs/src/lib.rs b/crates/uv-fs/src/lib.rs index f339e37c5..3ed23f66e 100644 --- a/crates/uv-fs/src/lib.rs +++ b/crates/uv-fs/src/lib.rs @@ -8,6 +8,7 @@ pub use crate::path::*; pub mod cachedir; mod path; +pub mod which; /// Reads data from the path and requires that it be valid UTF-8 or UTF-16. /// @@ -324,10 +325,10 @@ impl LockedFile { Ok(Self(file)) } Err(err) => { - // Log error code and enum kind to help debugging more exotic failures - // TODO(zanieb): When `raw_os_error` stabilizes, use that to avoid displaying - // the error when it is `WouldBlock`, which is expected and noisy otherwise. - trace!("Try lock error: {err:?}"); + // Log error code and enum kind to help debugging more exotic failures. + if err.kind() != std::io::ErrorKind::WouldBlock { + debug!("Try lock error: {err:?}"); + } info!( "Waiting to acquire lock for `{resource}` at `{}`", file.path().user_display(), diff --git a/crates/uv-fs/src/path.rs b/crates/uv-fs/src/path.rs index 4e5e8049e..54941df43 100644 --- a/crates/uv-fs/src/path.rs +++ b/crates/uv-fs/src/path.rs @@ -397,108 +397,4 @@ impl<'de> serde::de::Deserialize<'de> for PortablePathBuf { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_normalize_url() { - if cfg!(windows) { - assert_eq!( - normalize_url_path("/C:/Users/ferris/wheel-0.42.0.tar.gz"), - "C:\\Users\\ferris\\wheel-0.42.0.tar.gz" - ); - } else { - assert_eq!( - normalize_url_path("/C:/Users/ferris/wheel-0.42.0.tar.gz"), - "/C:/Users/ferris/wheel-0.42.0.tar.gz" - ); - } - - if cfg!(windows) { - assert_eq!( - normalize_url_path("./ferris/wheel-0.42.0.tar.gz"), - ".\\ferris\\wheel-0.42.0.tar.gz" - ); - } else { - assert_eq!( - normalize_url_path("./ferris/wheel-0.42.0.tar.gz"), - "./ferris/wheel-0.42.0.tar.gz" - ); - } - - if cfg!(windows) { - assert_eq!( - normalize_url_path("./wheel%20cache/wheel-0.42.0.tar.gz"), - ".\\wheel cache\\wheel-0.42.0.tar.gz" - ); - } else { - assert_eq!( - normalize_url_path("./wheel%20cache/wheel-0.42.0.tar.gz"), - "./wheel cache/wheel-0.42.0.tar.gz" - ); - } - } - - #[test] - fn test_normalize_path() { - let path = Path::new("/a/b/../c/./d"); - let normalized = normalize_absolute_path(path).unwrap(); - assert_eq!(normalized, Path::new("/a/c/d")); - - let path = Path::new("/a/../c/./d"); - let normalized = normalize_absolute_path(path).unwrap(); - assert_eq!(normalized, Path::new("/c/d")); - - // This should be an error. - let path = Path::new("/a/../../c/./d"); - let err = normalize_absolute_path(path).unwrap_err(); - assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); - } - - #[test] - fn test_relative_to() { - assert_eq!( - relative_to( - Path::new("/home/ferris/carcinization/lib/python/site-packages/foo/__init__.py"), - Path::new("/home/ferris/carcinization/lib/python/site-packages"), - ) - .unwrap(), - Path::new("foo/__init__.py") - ); - assert_eq!( - relative_to( - Path::new("/home/ferris/carcinization/lib/marker.txt"), - Path::new("/home/ferris/carcinization/lib/python/site-packages"), - ) - .unwrap(), - Path::new("../../marker.txt") - ); - assert_eq!( - relative_to( - Path::new("/home/ferris/carcinization/bin/foo_launcher"), - Path::new("/home/ferris/carcinization/lib/python/site-packages"), - ) - .unwrap(), - Path::new("../../../bin/foo_launcher") - ); - } - - #[test] - fn test_normalize_relative() { - let cases = [ - ( - "../../workspace-git-path-dep-test/packages/c/../../packages/d", - "../../workspace-git-path-dep-test/packages/d", - ), - ( - "workspace-git-path-dep-test/packages/c/../../packages/d", - "workspace-git-path-dep-test/packages/d", - ), - ("./a/../../b", "../b"), - ("/usr/../../foo", "/../foo"), - ]; - for (input, expected) in cases { - assert_eq!(normalize_path(Path::new(input)), Path::new(expected)); - } - } -} +mod tests; diff --git a/crates/uv-fs/src/path/tests.rs b/crates/uv-fs/src/path/tests.rs new file mode 100644 index 000000000..6d5f619df --- /dev/null +++ b/crates/uv-fs/src/path/tests.rs @@ -0,0 +1,103 @@ +use super::*; + +#[test] +fn test_normalize_url() { + if cfg!(windows) { + assert_eq!( + normalize_url_path("/C:/Users/ferris/wheel-0.42.0.tar.gz"), + "C:\\Users\\ferris\\wheel-0.42.0.tar.gz" + ); + } else { + assert_eq!( + normalize_url_path("/C:/Users/ferris/wheel-0.42.0.tar.gz"), + "/C:/Users/ferris/wheel-0.42.0.tar.gz" + ); + } + + if cfg!(windows) { + assert_eq!( + normalize_url_path("./ferris/wheel-0.42.0.tar.gz"), + ".\\ferris\\wheel-0.42.0.tar.gz" + ); + } else { + assert_eq!( + normalize_url_path("./ferris/wheel-0.42.0.tar.gz"), + "./ferris/wheel-0.42.0.tar.gz" + ); + } + + if cfg!(windows) { + assert_eq!( + normalize_url_path("./wheel%20cache/wheel-0.42.0.tar.gz"), + ".\\wheel cache\\wheel-0.42.0.tar.gz" + ); + } else { + assert_eq!( + normalize_url_path("./wheel%20cache/wheel-0.42.0.tar.gz"), + "./wheel cache/wheel-0.42.0.tar.gz" + ); + } +} + +#[test] +fn test_normalize_path() { + let path = Path::new("/a/b/../c/./d"); + let normalized = normalize_absolute_path(path).unwrap(); + assert_eq!(normalized, Path::new("/a/c/d")); + + let path = Path::new("/a/../c/./d"); + let normalized = normalize_absolute_path(path).unwrap(); + assert_eq!(normalized, Path::new("/c/d")); + + // This should be an error. + let path = Path::new("/a/../../c/./d"); + let err = normalize_absolute_path(path).unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); +} + +#[test] +fn test_relative_to() { + assert_eq!( + relative_to( + Path::new("/home/ferris/carcinization/lib/python/site-packages/foo/__init__.py"), + Path::new("/home/ferris/carcinization/lib/python/site-packages"), + ) + .unwrap(), + Path::new("foo/__init__.py") + ); + assert_eq!( + relative_to( + Path::new("/home/ferris/carcinization/lib/marker.txt"), + Path::new("/home/ferris/carcinization/lib/python/site-packages"), + ) + .unwrap(), + Path::new("../../marker.txt") + ); + assert_eq!( + relative_to( + Path::new("/home/ferris/carcinization/bin/foo_launcher"), + Path::new("/home/ferris/carcinization/lib/python/site-packages"), + ) + .unwrap(), + Path::new("../../../bin/foo_launcher") + ); +} + +#[test] +fn test_normalize_relative() { + let cases = [ + ( + "../../workspace-git-path-dep-test/packages/c/../../packages/d", + "../../workspace-git-path-dep-test/packages/d", + ), + ( + "workspace-git-path-dep-test/packages/c/../../packages/d", + "workspace-git-path-dep-test/packages/d", + ), + ("./a/../../b", "../b"), + ("/usr/../../foo", "/../foo"), + ]; + for (input, expected) in cases { + assert_eq!(normalize_path(Path::new(input)), Path::new(expected)); + } +} diff --git a/crates/uv-python/src/which.rs b/crates/uv-fs/src/which.rs similarity index 95% rename from crates/uv-python/src/which.rs rename to crates/uv-fs/src/which.rs index 352bc14f3..26a68203d 100644 --- a/crates/uv-python/src/which.rs +++ b/crates/uv-fs/src/which.rs @@ -3,7 +3,7 @@ use std::path::Path; /// Check whether a path in PATH is a valid executable. /// /// Derived from `which`'s `Checker`. -pub(crate) fn is_executable(path: &Path) -> bool { +pub fn is_executable(path: &Path) -> bool { #[cfg(any(unix, target_os = "wasi", target_os = "redox"))] { if rustix::fs::access(path, rustix::fs::Access::EXEC_OK).is_err() { diff --git a/crates/uv-git/Cargo.toml b/crates/uv-git/Cargo.toml index c311cf794..e7e20de8f 100644 --- a/crates/uv-git/Cargo.toml +++ b/crates/uv-git/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -16,6 +19,7 @@ workspace = true uv-cache-key = { workspace = true } uv-fs = { workspace = true, features = ["tokio"] } uv-auth = { workspace = true } +uv-static = { workspace = true} anyhow = { workspace = true } cargo-util = { workspace = true } @@ -28,3 +32,4 @@ thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } url = { workspace = true } +which = { workspace = true } diff --git a/crates/uv-git/src/git.rs b/crates/uv-git/src/git.rs index 9794dccc8..49a04af6e 100644 --- a/crates/uv-git/src/git.rs +++ b/crates/uv-git/src/git.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use std::path::{Path, PathBuf}; use std::str::{self, FromStr}; +use std::sync::LazyLock; use crate::sha::GitOid; use crate::GitSha; @@ -11,14 +12,19 @@ use anyhow::{anyhow, Context, Result}; use cargo_util::{paths, ProcessBuilder}; use reqwest::StatusCode; use reqwest_middleware::ClientWithMiddleware; + use tracing::debug; use url::Url; use uv_fs::Simplified; +use uv_static::EnvVars; /// A file indicates that if present, `git reset` has been done and a repo /// checkout is ready to go. See [`GitCheckout::reset`] for why we need this. const CHECKOUT_READY_LOCK: &str = ".ok"; +/// A global cache of the result of `which git`. +pub static GIT: LazyLock> = LazyLock::new(|| which::which("git")); + /// A reference to commit or commit-ish. #[derive( Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, @@ -157,7 +163,7 @@ impl GitRepository { /// Opens an existing Git repository at `path`. pub(crate) fn open(path: &Path) -> Result { // Make sure there is a Git repository at the specified path. - ProcessBuilder::new("git") + ProcessBuilder::new(GIT.as_ref()?) .arg("rev-parse") .cwd(path) .exec_with_output()?; @@ -176,7 +182,7 @@ impl GitRepository { // opts.external_template(false); // Initialize the repository. - ProcessBuilder::new("git") + ProcessBuilder::new(GIT.as_ref()?) .arg("init") .cwd(path) .exec_with_output()?; @@ -188,7 +194,7 @@ impl GitRepository { /// Parses the object ID of the given `refname`. fn rev_parse(&self, refname: &str) -> Result { - let result = ProcessBuilder::new("git") + let result = ProcessBuilder::new(GIT.as_ref()?) .arg("rev-parse") .arg(refname) .cwd(&self.path) @@ -294,7 +300,7 @@ impl GitDatabase { /// Get a short OID for a `revision`, usually 7 chars or more if ambiguous. pub(crate) fn to_short_id(&self, revision: GitOid) -> Result { - let output = ProcessBuilder::new("git") + let output = ProcessBuilder::new(GIT.as_ref()?) .arg("rev-parse") .arg("--short") .arg(revision.as_str()) @@ -371,7 +377,7 @@ impl GitCheckout { // Perform a local clone of the repository, which will attempt to use // hardlinks to set up the repository. This should speed up the clone operation // quite a bit if it works. - ProcessBuilder::new("git") + ProcessBuilder::new(GIT.as_ref()?) .arg("clone") .arg("--local") // Make sure to pass the local file path and not a file://... url. If given a url, @@ -417,7 +423,7 @@ impl GitCheckout { debug!("reset {} to {}", self.repo.path.display(), self.revision); // Perform the hard reset. - ProcessBuilder::new("git") + ProcessBuilder::new(GIT.as_ref()?) .arg("reset") .arg("--hard") .arg(self.revision.as_str()) @@ -425,7 +431,7 @@ impl GitCheckout { .exec_with_output()?; // Update submodules (`git submodule update --recursive`). - ProcessBuilder::new("git") + ProcessBuilder::new(GIT.as_ref()?) .arg("submodule") .arg("update") .arg("--recursive") @@ -591,7 +597,7 @@ fn fetch_with_cli( refspecs: &[String], tags: bool, ) -> Result<()> { - let mut cmd = ProcessBuilder::new("git"); + let mut cmd = ProcessBuilder::new(GIT.as_ref()?); cmd.arg("fetch"); if tags { cmd.arg("--tags"); @@ -604,13 +610,13 @@ fn fetch_with_cli( // rebase`), the GIT_DIR is set by git and will point to the wrong // location (this takes precedence over the cwd). Make sure this is // unset so git will look at cwd for the repo. - .env_remove("GIT_DIR") + .env_remove(EnvVars::GIT_DIR) // The reset of these may not be necessary, but I'm including them // just to be extra paranoid and avoid any issues. - .env_remove("GIT_WORK_TREE") - .env_remove("GIT_INDEX_FILE") - .env_remove("GIT_OBJECT_DIRECTORY") - .env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES") + .env_remove(EnvVars::GIT_WORK_TREE) + .env_remove(EnvVars::GIT_INDEX_FILE) + .env_remove(EnvVars::GIT_OBJECT_DIRECTORY) + .env_remove(EnvVars::GIT_ALTERNATE_OBJECT_DIRECTORIES) .cwd(&repo.path); // We capture the output to avoid streaming it to the user's console during clones. diff --git a/crates/uv-git/src/lib.rs b/crates/uv-git/src/lib.rs index 2ba6bca83..77b85ce99 100644 --- a/crates/uv-git/src/lib.rs +++ b/crates/uv-git/src/lib.rs @@ -1,7 +1,7 @@ use url::Url; pub use crate::credentials::{store_credentials_from_url, GIT_STORE}; -pub use crate::git::GitReference; +pub use crate::git::{GitReference, GIT}; pub use crate::resolver::{ GitResolver, GitResolverError, RepositoryReference, ResolvedRepositoryReference, }; diff --git a/crates/uv-git/src/sha.rs b/crates/uv-git/src/sha.rs index 5ae7d3ec8..11a5c91ae 100644 --- a/crates/uv-git/src/sha.rs +++ b/crates/uv-git/src/sha.rs @@ -112,19 +112,4 @@ impl Display for GitOid { } #[cfg(test)] -mod tests { - use std::str::FromStr; - - use super::{GitOid, OidParseError}; - - #[test] - fn git_oid() { - GitOid::from_str("4a23745badf5bf5ef7928f1e346e9986bd696d82").unwrap(); - - assert_eq!(GitOid::from_str(""), Err(OidParseError::Empty)); - assert_eq!( - GitOid::from_str(&str::repeat("a", 41)), - Err(OidParseError::TooLong) - ); - } -} +mod tests; diff --git a/crates/uv-git/src/sha/tests.rs b/crates/uv-git/src/sha/tests.rs new file mode 100644 index 000000000..cac5e187d --- /dev/null +++ b/crates/uv-git/src/sha/tests.rs @@ -0,0 +1,14 @@ +use std::str::FromStr; + +use super::{GitOid, OidParseError}; + +#[test] +fn git_oid() { + GitOid::from_str("4a23745badf5bf5ef7928f1e346e9986bd696d82").unwrap(); + + assert_eq!(GitOid::from_str(""), Err(OidParseError::Empty)); + assert_eq!( + GitOid::from_str(&str::repeat("a", 41)), + Err(OidParseError::TooLong) + ); +} diff --git a/crates/uv-install-wheel/Cargo.toml b/crates/uv-install-wheel/Cargo.toml index 349a12b53..97f5cf57a 100644 --- a/crates/uv-install-wheel/Cargo.toml +++ b/crates/uv-install-wheel/Cargo.toml @@ -17,6 +17,7 @@ license = { workspace = true } workspace = true [lib] +doctest = false name = "uv_install_wheel" [dependencies] diff --git a/crates/uv-install-wheel/src/lib.rs b/crates/uv-install-wheel/src/lib.rs index 72c589f62..83300d42d 100644 --- a/crates/uv-install-wheel/src/lib.rs +++ b/crates/uv-install-wheel/src/lib.rs @@ -67,7 +67,7 @@ pub enum Error { RecordFile(String), #[error("RECORD file is invalid")] RecordCsv(#[from] csv::Error), - #[error("Broken virtualenv: {0}")] + #[error("Broken virtual environment: {0}")] BrokenVenv(String), #[error( "Unable to create Windows launcher for: {0} (only x86_64, x86, and arm64 are supported)" diff --git a/crates/uv-install-wheel/src/linker.rs b/crates/uv-install-wheel/src/linker.rs index 569aeb33e..af00dc612 100644 --- a/crates/uv-install-wheel/src/linker.rs +++ b/crates/uv-install-wheel/src/linker.rs @@ -17,7 +17,7 @@ use reflink_copy as reflink; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use tempfile::tempdir_in; -use tracing::{debug, instrument}; +use tracing::{debug, instrument, trace}; use uv_cache_info::CacheInfo; use uv_distribution_filename::WheelFilename; use uv_pypi_types::{DirectUrl, Metadata12}; @@ -74,13 +74,13 @@ pub fn install_wheel( // > 1.c If Root-Is-Purelib == ‘true’, unpack archive into purelib (site-packages). // > 1.d Else unpack archive into platlib (site-packages). - debug!(?name, "Extracting file"); + trace!(?name, "Extracting file"); let site_packages = match lib_kind { LibKind::Pure => &layout.scheme.purelib, LibKind::Plat => &layout.scheme.platlib, }; let num_unpacked = link_mode.link_wheel_files(site_packages, &wheel, locks)?; - debug!(?name, "Extracted {num_unpacked} files"); + trace!(?name, "Extracted {num_unpacked} files"); // Read the RECORD file. let mut record_file = File::open( @@ -94,9 +94,9 @@ pub fn install_wheel( parse_scripts(&wheel, &dist_info_prefix, None, layout.python_version.1)?; if console_scripts.is_empty() && gui_scripts.is_empty() { - debug!(?name, "No entrypoints"); + trace!(?name, "No entrypoints"); } else { - debug!(?name, "Writing entrypoints"); + trace!(?name, "Writing entrypoints"); fs_err::create_dir_all(&layout.scheme.scripts)?; write_script_entrypoints( @@ -121,7 +121,7 @@ pub fn install_wheel( // 2.b Move each subtree of distribution-1.0.data/ onto its destination path. Each subdirectory of distribution-1.0.data/ is a key into a dict of destination directories, such as distribution-1.0.data/(purelib|platlib|headers|scripts|data). The initially supported paths are taken from distutils.command.install. let data_dir = site_packages.join(format!("{dist_info_prefix}.data")); if data_dir.is_dir() { - debug!(?name, "Installing data"); + trace!(?name, "Installing data"); install_data( layout, relocatable, @@ -137,10 +137,10 @@ pub fn install_wheel( // 2.e Remove empty distribution-1.0.data directory. fs::remove_dir_all(data_dir)?; } else { - debug!(?name, "No data"); + trace!(?name, "No data"); } - debug!(?name, "Writing extra metadata"); + trace!(?name, "Writing extra metadata"); extra_dist_info( site_packages, &dist_info_prefix, @@ -151,7 +151,7 @@ pub fn install_wheel( &mut record, )?; - debug!(?name, "Writing record"); + trace!(?name, "Writing record"); let mut record_writer = csv::WriterBuilder::new() .has_headers(false) .escape(b'"') @@ -370,7 +370,7 @@ fn clone_recursive( let from = entry.path(); let to = site_packages.join(from.strip_prefix(wheel).unwrap()); - debug!("Cloning {} to {}", from.display(), to.display()); + trace!("Cloning {} to {}", from.display(), to.display()); if (cfg!(windows) || cfg!(target_os = "linux")) && from.is_dir() { // On Windows, reflinking directories is not supported, so we copy each file instead. diff --git a/crates/uv-install-wheel/src/script.rs b/crates/uv-install-wheel/src/script.rs index 34039ad2c..cc4dbc26f 100644 --- a/crates/uv-install-wheel/src/script.rs +++ b/crates/uv-install-wheel/src/script.rs @@ -8,7 +8,7 @@ use crate::{wheel, Error}; /// A script defining the name of the runnable entrypoint and the module and function that should be /// run. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] pub(crate) struct Script { pub(crate) name: String, pub(crate) module: String, @@ -94,7 +94,7 @@ pub(crate) fn scripts_from_ini( let Some((left, right)) = script.name.split_once('.') else { return true; }; - !(left == "pip3" || right.parse::().is_ok()) + !(left == "pip3" && right.parse::().is_ok()) }); // ... either has a `pip3` launcher we can use as template for the `pip3.x` users expect. if let Some(pip_script) = console_scripts.iter().find(|script| script.name == "pip3") { @@ -109,7 +109,7 @@ pub(crate) fn scripts_from_ini( #[cfg(test)] mod test { - use crate::script::Script; + use crate::script::{scripts_from_ini, Script}; #[test] fn test_valid_script_names() { @@ -148,4 +148,74 @@ mod test { assert_eq!(script.function, "mod_bar.sub_foo.func_baz"); assert_eq!(script.import_name(), "mod_bar"); } + + #[test] + fn test_pip3_entry_point_modification() { + // Notably pip only applies the modification hack to entry points + // called "pip" and "easy_install" -- if there are abi3 style wheels + // that contain other versioned entry points, they keep their (probably + // incorrect) suffixes. + let sample_ini = " +[console_scripts] +pip = a:b1 +pip3 = a:b2 +pip3.11 = a:b3 +pip3.x = a:b4 +pip4.11 = a:b5 +memray = a:b6 +memray3.11 = a:b7 +"; + let (mut console_scripts, _gui_scripts) = + scripts_from_ini(None, 99, sample_ini.to_string()).unwrap(); + console_scripts.sort(); + + assert_eq!( + Some(&Script { + name: "memray".to_string(), + module: "a".to_string(), + function: "b6".to_string() + }), + console_scripts.first() + ); + assert_eq!( + Some(&Script { + name: "memray3.11".to_string(), + module: "a".to_string(), + function: "b7".to_string() + }), + console_scripts.get(1) + ); + assert_eq!( + Some(&Script { + name: "pip".to_string(), + module: "a".to_string(), + function: "b1".to_string() + }), + console_scripts.get(2) + ); + assert_eq!( + Some(&Script { + name: "pip3".to_string(), + module: "a".to_string(), + function: "b2".to_string() + }), + console_scripts.get(3) + ); + assert_eq!( + Some(&Script { + name: "pip3.99".to_string(), + module: "a".to_string(), + function: "b2".to_string() + }), + console_scripts.get(4) + ); + assert_eq!( + Some(&Script { + name: "pip3.x".to_string(), + module: "a".to_string(), + function: "b4".to_string() + }), + console_scripts.get(5) + ); + } } diff --git a/crates/uv-install-wheel/src/uninstall.rs b/crates/uv-install-wheel/src/uninstall.rs index a8aa69f43..15bbdc3f6 100644 --- a/crates/uv-install-wheel/src/uninstall.rs +++ b/crates/uv-install-wheel/src/uninstall.rs @@ -3,7 +3,7 @@ use std::path::{Component, Path, PathBuf}; use fs_err as fs; use std::sync::{LazyLock, Mutex}; -use tracing::debug; +use tracing::trace; use uv_fs::write_atomic_sync; use crate::wheel::read_record_file; @@ -39,7 +39,7 @@ pub fn uninstall_wheel(dist_info: &Path) -> Result { let path = site_packages.join(&entry.path); match fs::remove_file(&path) { Ok(()) => { - debug!("Removed file: {}", path.display()); + trace!("Removed file: {}", path.display()); file_count += 1; if let Some(parent) = path.parent() { visited.insert(normalize_path(parent)); @@ -48,7 +48,7 @@ pub fn uninstall_wheel(dist_info: &Path) -> Result { Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} Err(err) => match fs::remove_dir_all(&path) { Ok(()) => { - debug!("Removed directory: {}", path.display()); + trace!("Removed directory: {}", path.display()); dir_count += 1; } Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} @@ -81,7 +81,7 @@ pub fn uninstall_wheel(dist_info: &Path) -> Result { let pycache = path.join("__pycache__"); match fs::remove_dir_all(&pycache) { Ok(()) => { - debug!("Removed directory: {}", pycache.display()); + trace!("Removed directory: {}", pycache.display()); dir_count += 1; } Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} @@ -103,7 +103,7 @@ pub fn uninstall_wheel(dist_info: &Path) -> Result { fs::remove_dir(path)?; - debug!("Removed directory: {}", path.display()); + trace!("Removed directory: {}", path.display()); dir_count += 1; if let Some(parent) = path.parent() { @@ -169,7 +169,7 @@ pub fn uninstall_egg(egg_info: &Path) -> Result { // Remove as a directory. match fs_err::remove_dir_all(&path) { Ok(()) => { - debug!("Removed directory: {}", path.display()); + trace!("Removed directory: {}", path.display()); dir_count += 1; continue; } @@ -182,7 +182,7 @@ pub fn uninstall_egg(egg_info: &Path) -> Result { let path = path.with_extension(extension); match fs_err::remove_file(&path) { Ok(()) => { - debug!("Removed file: {}", path.display()); + trace!("Removed file: {}", path.display()); file_count += 1; break; } @@ -195,7 +195,7 @@ pub fn uninstall_egg(egg_info: &Path) -> Result { // Remove the `.egg-info` directory. match fs_err::remove_dir_all(egg_info) { Ok(()) => { - debug!("Removed directory: {}", egg_info.display()); + trace!("Removed directory: {}", egg_info.display()); dir_count += 1; } Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} @@ -245,7 +245,7 @@ pub fn uninstall_legacy_editable(egg_link: &Path) -> Result { match fs::remove_file(egg_link) { Ok(()) => { - debug!("Removed file: {}", egg_link.display()); + trace!("Removed file: {}", egg_link.display()); file_count += 1; } Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} @@ -277,7 +277,7 @@ pub fn uninstall_legacy_editable(egg_link: &Path) -> Result { } if removed { write_atomic_sync(&easy_install, new_content)?; - debug!("Removed line from `easy-install.pth`: {target_line}"); + trace!("Removed line from `easy-install.pth`: {target_line}"); } Ok(Uninstall { diff --git a/crates/uv-install-wheel/src/wheel.rs b/crates/uv-install-wheel/src/wheel.rs index 8d518b9e0..559237e16 100644 --- a/crates/uv-install-wheel/src/wheel.rs +++ b/crates/uv-install-wheel/src/wheel.rs @@ -143,7 +143,7 @@ fn format_shebang(executable: impl AsRef, os_name: &str, relocatable: bool // (note: the Windows trampoline binaries natively support relative paths to executable) if shebang_length > 127 || executable.contains(' ') || relocatable { let prefix = if relocatable { - r#""$(CDPATH= cd -- "$(dirname -- "$0")" && echo "$PWD")"/"# + r#""$(dirname -- "$(realpath -- "$0")")"/"# } else { "" }; @@ -1019,7 +1019,7 @@ mod test { let os_name = "posix"; assert_eq!( format_shebang(executable, os_name, true), - "#!/bin/sh\n'''exec' \"$(CDPATH= cd -- \"$(dirname -- \"$0\")\" && echo \"$PWD\")\"/'python3' \"$0\" \"$@\"\n' '''" + "#!/bin/sh\n'''exec' \"$(dirname -- \"$(realpath -- \"$0\")\")\"/'python3' \"$0\" \"$@\"\n' '''" ); // Except on Windows... diff --git a/crates/uv-installer/Cargo.toml b/crates/uv-installer/Cargo.toml index 502bd88f2..12a1114b7 100644 --- a/crates/uv-installer/Cargo.toml +++ b/crates/uv-installer/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -28,6 +31,7 @@ uv-pep508 = { workspace = true } uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } uv-python = { workspace = true } +uv-static = { workspace = true } uv-types = { workspace = true } uv-warnings = { workspace = true } diff --git a/crates/uv-installer/src/compile.rs b/crates/uv-installer/src/compile.rs index 182b131ab..2fc2d87b2 100644 --- a/crates/uv-installer/src/compile.rs +++ b/crates/uv-installer/src/compile.rs @@ -15,6 +15,7 @@ use tracing::{debug, instrument}; use walkdir::WalkDir; use uv_fs::Simplified; +use uv_static::EnvVars; use uv_warnings::warn_user; const COMPILEALL_SCRIPT: &str = include_str!("pip_compileall.py"); @@ -266,7 +267,7 @@ async fn launch_bytecode_compiler( .stderr(Stdio::piped()) .current_dir(dir) // Otherwise stdout is buffered and we'll wait forever for a response - .env("PYTHONUNBUFFERED", "1") + .env(EnvVars::PYTHONUNBUFFERED, "1") .spawn() .map_err(CompileError::PythonSubcommand)?; diff --git a/crates/uv-installer/src/plan.rs b/crates/uv-installer/src/plan.rs index b1241355d..cd654206c 100644 --- a/crates/uv-installer/src/plan.rs +++ b/crates/uv-installer/src/plan.rs @@ -121,6 +121,9 @@ impl<'a> Planner<'a> { match installable { Dist::Built(BuiltDist::Registry(wheel)) => { if let Some(distribution) = registry_index.get(wheel.name()).find_map(|entry| { + if *entry.index.url() != wheel.best_wheel().index { + return None; + } if entry.dist.filename.version != wheel.best_wheel().filename.version { return None; }; @@ -231,6 +234,9 @@ impl<'a> Planner<'a> { } Dist::Source(SourceDist::Registry(sdist)) => { if let Some(distribution) = registry_index.get(sdist.name()).find_map(|entry| { + if *entry.index.url() != sdist.index { + return None; + } if entry.dist.filename.version != sdist.version { return None; }; diff --git a/crates/uv-installer/src/preparer.rs b/crates/uv-installer/src/preparer.rs index 8c3bc2787..de9cd4b48 100644 --- a/crates/uv-installer/src/preparer.rs +++ b/crates/uv-installer/src/preparer.rs @@ -11,7 +11,8 @@ use uv_cache::Cache; use uv_configuration::BuildOptions; use uv_distribution::{DistributionDatabase, LocalWheel}; use uv_distribution_types::{ - BuildableSource, CachedDist, Dist, Hashed, Identifier, Name, RemoteSource, + BuildableSource, BuiltDist, CachedDist, Dist, Hashed, Identifier, Name, RemoteSource, + SourceDist, }; use uv_platform_tags::Tags; use uv_types::{BuildContext, HashStrategy, InFlight}; @@ -24,8 +25,12 @@ pub enum Error { NoBinary(PackageName), #[error("Failed to unzip wheel: {0}")] Unzip(Dist, #[source] Box), - #[error("Failed to fetch wheel: {0}")] - Fetch(Dist, #[source] Box), + #[error("Failed to download `{0}`")] + Fetch(BuiltDist, #[source] Box), + #[error("Failed to download and build `{0}`")] + FetchAndBuild(SourceDist, #[source] Box), + #[error("Failed to build `{0}`")] + Build(SourceDist, #[source] Box), /// Should not occur; only seen when another task panicked. #[error("The task executor is broken, did some other task panic?")] Join(#[from] JoinError), @@ -150,20 +155,36 @@ impl<'a, Context: BuildContext> Preparer<'a, Context> { .database .get_or_build_wheel(&dist, self.tags, policy) .boxed_local() - .map_err(|err| Error::Fetch(dist.clone(), Box::new(err))) + .map_err(|err| match &dist { + Dist::Built(dist) => Error::Fetch(dist.clone(), Box::new(err)), + Dist::Source(dist) => { + if dist.is_local() { + Error::Build(dist.clone(), Box::new(err)) + } else { + Error::FetchAndBuild(dist.clone(), Box::new(err)) + } + } + }) .await .and_then(|wheel: LocalWheel| { if wheel.satisfies(policy) { Ok(wheel) } else { - Err(Error::Fetch( - dist.clone(), - Box::new(uv_distribution::Error::hash_mismatch( - dist.to_string(), - policy.digests(), - wheel.hashes(), - )), - )) + let err = uv_distribution::Error::hash_mismatch( + dist.to_string(), + policy.digests(), + wheel.hashes(), + ); + Err(match &dist { + Dist::Built(dist) => Error::Fetch(dist.clone(), Box::new(err)), + Dist::Source(dist) => { + if dist.is_local() { + Error::Build(dist.clone(), Box::new(err)) + } else { + Error::FetchAndBuild(dist.clone(), Box::new(err)) + } + } + }) } }) .map(CachedDist::from); diff --git a/crates/uv-macros/Cargo.toml b/crates/uv-macros/Cargo.toml index 3a3b6f971..38e4be48c 100644 --- a/crates/uv-macros/Cargo.toml +++ b/crates/uv-macros/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [lib] proc-macro = true +doctest = false [lints] workspace = true diff --git a/crates/uv-metadata/Cargo.toml b/crates/uv-metadata/Cargo.toml index c782a8df5..ddbf5b5aa 100644 --- a/crates/uv-metadata/Cargo.toml +++ b/crates/uv-metadata/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true authors.workspace = true license.workspace = true +[lib] +doctest = false + [dependencies] uv-distribution-filename = { workspace = true } uv-normalize = { workspace = true } diff --git a/crates/uv-normalize/Cargo.toml b/crates/uv-normalize/Cargo.toml index 0d1bdec42..8f4db6a15 100644 --- a/crates/uv-normalize/Cargo.toml +++ b/crates/uv-normalize/Cargo.toml @@ -4,6 +4,9 @@ version = "0.0.1" edition = "2021" description = "Normalization for distribution, package and extra names." +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-normalize/src/dist_info_name.rs b/crates/uv-normalize/src/dist_info_name.rs index 0340cc677..f885589e7 100644 --- a/crates/uv-normalize/src/dist_info_name.rs +++ b/crates/uv-normalize/src/dist_info_name.rs @@ -86,23 +86,4 @@ impl AsRef for DistInfoName<'_> { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn normalize() { - let inputs = [ - "friendly-bard", - "Friendly-Bard", - "FRIENDLY-BARD", - "friendly.bard", - "friendly_bard", - "friendly--bard", - "friendly-.bard", - "FrIeNdLy-._.-bArD", - ]; - for input in inputs { - assert_eq!(DistInfoName::normalize(input), "friendly-bard"); - } - } -} +mod tests; diff --git a/crates/uv-normalize/src/dist_info_name/tests.rs b/crates/uv-normalize/src/dist_info_name/tests.rs new file mode 100644 index 000000000..156b0887a --- /dev/null +++ b/crates/uv-normalize/src/dist_info_name/tests.rs @@ -0,0 +1,18 @@ +use super::*; + +#[test] +fn normalize() { + let inputs = [ + "friendly-bard", + "Friendly-Bard", + "FRIENDLY-BARD", + "friendly.bard", + "friendly_bard", + "friendly--bard", + "friendly-.bard", + "FrIeNdLy-._.-bArD", + ]; + for input in inputs { + assert_eq!(DistInfoName::normalize(input), "friendly-bard"); + } +} diff --git a/crates/uv-normalize/src/lib.rs b/crates/uv-normalize/src/lib.rs index 0360a5a1a..29c6480a8 100644 --- a/crates/uv-normalize/src/lib.rs +++ b/crates/uv-normalize/src/lib.rs @@ -120,79 +120,4 @@ impl Display for InvalidNameError { impl Error for InvalidNameError {} #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn normalize() { - let inputs = [ - "friendly-bard", - "Friendly-Bard", - "FRIENDLY-BARD", - "friendly.bard", - "friendly_bard", - "friendly--bard", - "friendly-.bard", - "FrIeNdLy-._.-bArD", - ]; - for input in inputs { - assert_eq!(validate_and_normalize_ref(input).unwrap(), "friendly-bard"); - assert_eq!( - validate_and_normalize_owned(input.to_string()).unwrap(), - "friendly-bard" - ); - } - } - - #[test] - fn check() { - let inputs = ["friendly-bard", "friendlybard"]; - for input in inputs { - assert!(is_normalized(input).unwrap(), "{input:?}"); - } - - let inputs = [ - "friendly.bard", - "friendly.BARD", - "friendly_bard", - "friendly--bard", - "friendly-.bard", - "FrIeNdLy-._.-bArD", - ]; - for input in inputs { - assert!(!is_normalized(input).unwrap(), "{input:?}"); - } - } - - #[test] - fn unchanged() { - // Unchanged - let unchanged = ["friendly-bard", "1okay", "okay2"]; - for input in unchanged { - assert_eq!(validate_and_normalize_ref(input).unwrap(), input); - assert_eq!( - validate_and_normalize_owned(input.to_string()).unwrap(), - input - ); - assert!(is_normalized(input).unwrap()); - } - } - - #[test] - fn failures() { - let failures = [ - " starts-with-space", - "-starts-with-dash", - "ends-with-dash-", - "ends-with-space ", - "includes!invalid-char", - "space in middle", - "alpha-α", - ]; - for input in failures { - assert!(validate_and_normalize_ref(input).is_err()); - assert!(validate_and_normalize_owned(input.to_string()).is_err()); - assert!(is_normalized(input).is_err()); - } - } -} +mod tests; diff --git a/crates/uv-normalize/src/tests.rs b/crates/uv-normalize/src/tests.rs new file mode 100644 index 000000000..96368fcc7 --- /dev/null +++ b/crates/uv-normalize/src/tests.rs @@ -0,0 +1,74 @@ +use super::*; + +#[test] +fn normalize() { + let inputs = [ + "friendly-bard", + "Friendly-Bard", + "FRIENDLY-BARD", + "friendly.bard", + "friendly_bard", + "friendly--bard", + "friendly-.bard", + "FrIeNdLy-._.-bArD", + ]; + for input in inputs { + assert_eq!(validate_and_normalize_ref(input).unwrap(), "friendly-bard"); + assert_eq!( + validate_and_normalize_owned(input.to_string()).unwrap(), + "friendly-bard" + ); + } +} + +#[test] +fn check() { + let inputs = ["friendly-bard", "friendlybard"]; + for input in inputs { + assert!(is_normalized(input).unwrap(), "{input:?}"); + } + + let inputs = [ + "friendly.bard", + "friendly.BARD", + "friendly_bard", + "friendly--bard", + "friendly-.bard", + "FrIeNdLy-._.-bArD", + ]; + for input in inputs { + assert!(!is_normalized(input).unwrap(), "{input:?}"); + } +} + +#[test] +fn unchanged() { + // Unchanged + let unchanged = ["friendly-bard", "1okay", "okay2"]; + for input in unchanged { + assert_eq!(validate_and_normalize_ref(input).unwrap(), input); + assert_eq!( + validate_and_normalize_owned(input.to_string()).unwrap(), + input + ); + assert!(is_normalized(input).unwrap()); + } +} + +#[test] +fn failures() { + let failures = [ + " starts-with-space", + "-starts-with-dash", + "ends-with-dash-", + "ends-with-space ", + "includes!invalid-char", + "space in middle", + "alpha-α", + ]; + for input in failures { + assert!(validate_and_normalize_ref(input).is_err()); + assert!(validate_and_normalize_owned(input.to_string()).is_err()); + assert!(is_normalized(input).is_err()); + } +} diff --git a/crates/uv-once-map/Cargo.toml b/crates/uv-once-map/Cargo.toml index 03b877bf7..4ed767cd3 100644 --- a/crates/uv-once-map/Cargo.toml +++ b/crates/uv-once-map/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-once-map/src/lib.rs b/crates/uv-once-map/src/lib.rs index 2de152619..2568de59e 100644 --- a/crates/uv-once-map/src/lib.rs +++ b/crates/uv-once-map/src/lib.rs @@ -105,6 +105,18 @@ impl OnceMap { Value::Waiting(_) => None, } } + + /// Remove the result of a previous job, if any. + pub fn remove(&self, key: &Q) -> Option + where + K: Borrow, + { + let entry = self.items.remove(key)?; + match entry { + (_, Value::Filled(value)) => Some(value), + (_, Value::Waiting(_)) => None, + } + } } impl Default for OnceMap { diff --git a/crates/uv-options-metadata/Cargo.toml b/crates/uv-options-metadata/Cargo.toml index 24fc367c0..a159ce16d 100644 --- a/crates/uv-options-metadata/Cargo.toml +++ b/crates/uv-options-metadata/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-options-metadata/src/lib.rs b/crates/uv-options-metadata/src/lib.rs index 8f7e9ec62..fad3b9921 100644 --- a/crates/uv-options-metadata/src/lib.rs +++ b/crates/uv-options-metadata/src/lib.rs @@ -100,76 +100,6 @@ impl OptionSet { /// Returns `true` if this set has an option that resolves to `name`. /// /// The name can be separated by `.` to find a nested option. - /// - /// ## Examples - /// - /// ### Test for the existence of a child option - /// - /// ```rust - /// # use uv_options_metadata::{OptionField, OptionsMetadata, Visit}; - /// - /// struct WithOptions; - /// - /// impl OptionsMetadata for WithOptions { - /// fn record(visit: &mut dyn Visit) { - /// visit.record_field("ignore-git-ignore", OptionField { - /// doc: "Whether Ruff should respect the gitignore file", - /// default: "false", - /// value_type: "bool", - /// example: "", - /// scope: None, - /// deprecated: None, - /// possible_values: None - /// }); - /// } - /// } - /// - /// assert!(WithOptions::metadata().has("ignore-git-ignore")); - /// assert!(!WithOptions::metadata().has("does-not-exist")); - /// ``` - /// ### Test for the existence of a nested option - /// - /// ```rust - /// # use uv_options_metadata::{OptionField, OptionsMetadata, Visit}; - /// - /// struct Root; - /// - /// impl OptionsMetadata for Root { - /// fn record(visit: &mut dyn Visit) { - /// visit.record_field("ignore-git-ignore", OptionField { - /// doc: "Whether Ruff should respect the gitignore file", - /// default: "false", - /// value_type: "bool", - /// example: "", - /// scope: None, - /// deprecated: None, - /// possible_values: None - /// }); - /// - /// visit.record_set("format", Nested::metadata()); - /// } - /// } - /// - /// struct Nested; - /// - /// impl OptionsMetadata for Nested { - /// fn record(visit: &mut dyn Visit) { - /// visit.record_field("hard-tabs", OptionField { - /// doc: "Use hard tabs for indentation and spaces for alignment.", - /// default: "false", - /// value_type: "bool", - /// example: "", - /// scope: None, - /// deprecated: None, - /// possible_values: None - /// }); - /// } - /// } - /// - /// assert!(Root::metadata().has("format.hard-tabs")); - /// assert!(!Root::metadata().has("format.spaces")); - /// assert!(!Root::metadata().has("lint.hard-tabs")); - /// ``` pub fn has(&self, name: &str) -> bool { self.find(name).is_some() } @@ -177,81 +107,6 @@ impl OptionSet { /// Returns `Some` if this set has an option that resolves to `name` and `None` otherwise. /// /// The name can be separated by `.` to find a nested option. - /// - /// ## Examples - /// - /// ### Find a child option - /// - /// ```rust - /// # use uv_options_metadata::{OptionEntry, OptionField, OptionsMetadata, Visit}; - /// - /// struct WithOptions; - /// - /// static IGNORE_GIT_IGNORE: OptionField = OptionField { - /// doc: "Whether Ruff should respect the gitignore file", - /// default: "false", - /// value_type: "bool", - /// example: "", - /// scope: None, - /// deprecated: None, - /// possible_values: None - /// }; - /// - /// impl OptionsMetadata for WithOptions { - /// fn record(visit: &mut dyn Visit) { - /// visit.record_field("ignore-git-ignore", IGNORE_GIT_IGNORE.clone()); - /// } - /// } - /// - /// assert_eq!(WithOptions::metadata().find("ignore-git-ignore"), Some(OptionEntry::Field(IGNORE_GIT_IGNORE.clone()))); - /// assert_eq!(WithOptions::metadata().find("does-not-exist"), None); - /// ``` - /// ### Find a nested option - /// - /// ```rust - /// # use uv_options_metadata::{OptionEntry, OptionField, OptionsMetadata, Visit}; - /// - /// static HARD_TABS: OptionField = OptionField { - /// doc: "Use hard tabs for indentation and spaces for alignment.", - /// default: "false", - /// value_type: "bool", - /// example: "", - /// scope: None, - /// deprecated: None, - /// possible_values: None - /// }; - /// - /// struct Root; - /// - /// impl OptionsMetadata for Root { - /// fn record(visit: &mut dyn Visit) { - /// visit.record_field("ignore-git-ignore", OptionField { - /// doc: "Whether Ruff should respect the gitignore file", - /// default: "false", - /// value_type: "bool", - /// example: "", - /// scope: None, - /// deprecated: None, - /// possible_values: None - /// }); - /// - /// visit.record_set("format", Nested::metadata()); - /// } - /// } - /// - /// struct Nested; - /// - /// impl OptionsMetadata for Nested { - /// fn record(visit: &mut dyn Visit) { - /// visit.record_field("hard-tabs", HARD_TABS.clone()); - /// } - /// } - /// - /// assert_eq!(Root::metadata().find("format.hard-tabs"), Some(OptionEntry::Field(HARD_TABS.clone()))); - /// assert_eq!(Root::metadata().find("format"), Some(OptionEntry::Set(Nested::metadata()))); - /// assert_eq!(Root::metadata().find("format.spaces"), None); - /// assert_eq!(Root::metadata().find("lint.hard-tabs"), None); - /// ``` pub fn find(&self, name: &str) -> Option { struct FindOptionVisitor<'a> { option: Option, @@ -459,3 +314,6 @@ impl Display for PossibleValue { Ok(()) } } + +#[cfg(test)] +mod tests; diff --git a/crates/uv-options-metadata/src/tests.rs b/crates/uv-options-metadata/src/tests.rs new file mode 100644 index 000000000..20374f0af --- /dev/null +++ b/crates/uv-options-metadata/src/tests.rs @@ -0,0 +1,153 @@ +use super::*; + +#[test] +fn test_has_child_option() { + struct WithOptions; + + impl OptionsMetadata for WithOptions { + fn record(visit: &mut dyn Visit) { + visit.record_field( + "ignore-git-ignore", + OptionField { + doc: "Whether Ruff should respect the gitignore file", + default: "false", + value_type: "bool", + example: "", + scope: None, + deprecated: None, + possible_values: None, + }, + ); + } + } + + assert!(WithOptions::metadata().has("ignore-git-ignore")); + assert!(!WithOptions::metadata().has("does-not-exist")); +} + +#[test] +fn test_has_nested_option() { + struct Root; + + impl OptionsMetadata for Root { + fn record(visit: &mut dyn Visit) { + visit.record_field( + "ignore-git-ignore", + OptionField { + doc: "Whether Ruff should respect the gitignore file", + default: "false", + value_type: "bool", + example: "", + scope: None, + deprecated: None, + possible_values: None, + }, + ); + + visit.record_set("format", Nested::metadata()); + } + } + + struct Nested; + + impl OptionsMetadata for Nested { + fn record(visit: &mut dyn Visit) { + visit.record_field( + "hard-tabs", + OptionField { + doc: "Use hard tabs for indentation and spaces for alignment.", + default: "false", + value_type: "bool", + example: "", + scope: None, + deprecated: None, + possible_values: None, + }, + ); + } + } + + assert!(Root::metadata().has("format.hard-tabs")); + assert!(!Root::metadata().has("format.spaces")); + assert!(!Root::metadata().has("lint.hard-tabs")); +} + +#[test] +fn test_find_child_option() { + struct WithOptions; + + static IGNORE_GIT_IGNORE: OptionField = OptionField { + doc: "Whether Ruff should respect the gitignore file", + default: "false", + value_type: "bool", + example: "", + scope: None, + deprecated: None, + possible_values: None, + }; + + impl OptionsMetadata for WithOptions { + fn record(visit: &mut dyn Visit) { + visit.record_field("ignore-git-ignore", IGNORE_GIT_IGNORE.clone()); + } + } + + assert_eq!( + WithOptions::metadata().find("ignore-git-ignore"), + Some(OptionEntry::Field(IGNORE_GIT_IGNORE.clone())) + ); + assert_eq!(WithOptions::metadata().find("does-not-exist"), None); +} + +#[test] +fn test_find_nested_option() { + static HARD_TABS: OptionField = OptionField { + doc: "Use hard tabs for indentation and spaces for alignment.", + default: "false", + value_type: "bool", + example: "", + scope: None, + deprecated: None, + possible_values: None, + }; + + struct Root; + + impl OptionsMetadata for Root { + fn record(visit: &mut dyn Visit) { + visit.record_field( + "ignore-git-ignore", + OptionField { + doc: "Whether Ruff should respect the gitignore file", + default: "false", + value_type: "bool", + example: "", + scope: None, + deprecated: None, + possible_values: None, + }, + ); + + visit.record_set("format", Nested::metadata()); + } + } + + struct Nested; + + impl OptionsMetadata for Nested { + fn record(visit: &mut dyn Visit) { + visit.record_field("hard-tabs", HARD_TABS.clone()); + } + } + + assert_eq!( + Root::metadata().find("format.hard-tabs"), + Some(OptionEntry::Field(HARD_TABS.clone())) + ); + assert_eq!( + Root::metadata().find("format"), + Some(OptionEntry::Set(Nested::metadata())) + ); + assert_eq!(Root::metadata().find("format.spaces"), None); + assert_eq!(Root::metadata().find("lint.hard-tabs"), None); +} diff --git a/crates/uv-pep440/Cargo.toml b/crates/uv-pep440/Cargo.toml index 47dd3b26d..001009c02 100644 --- a/crates/uv-pep440/Cargo.toml +++ b/crates/uv-pep440/Cargo.toml @@ -14,19 +14,21 @@ authors = { workspace = true } [lib] name = "uv_pep440" crate-type = ["rlib", "cdylib"] +doctest = false [lints] workspace = true [dependencies] serde = { workspace = true, features = ["derive"] } -rkyv = { workspace = true } +rkyv = { workspace = true, optional = true } tracing = { workspace = true, optional = true } unicode-width = { workspace = true } unscanny = { workspace = true } [dev-dependencies] indoc = { version = "2.0.5" } +tracing = { workspace = true } [features] # Match the API of the published crate, for compatibility. diff --git a/crates/uv-pep440/src/lib.rs b/crates/uv-pep440/src/lib.rs index e04b52f69..ca848d382 100644 --- a/crates/uv-pep440/src/lib.rs +++ b/crates/uv-pep440/src/lib.rs @@ -1,17 +1,6 @@ //! A library for python version numbers and specifiers, implementing //! [PEP 440](https://peps.python.org/pep-0440) //! -//! ```rust -//! use std::str::FromStr; -//! use uv_pep440::{VersionSpecifiers, Version, VersionSpecifier}; -//! -//! let version = Version::from_str("1.19").unwrap(); -//! let version_specifier = VersionSpecifier::from_str("== 1.*").unwrap(); -//! assert!(version_specifier.contains(&version)); -//! let version_specifiers = VersionSpecifiers::from_str(">=1.16, <2.0").unwrap(); -//! assert!(version_specifiers.contains(&version)); -//! ``` -//! //! PEP 440 has a lot of unintuitive features, including: //! //! * An epoch that you can prefix the version which, e.g. `1!1.2.3`. Lower epoch always means lower @@ -47,3 +36,6 @@ pub use { mod version; mod version_specifier; + +#[cfg(test)] +mod tests; diff --git a/crates/uv-pep440/src/tests.rs b/crates/uv-pep440/src/tests.rs new file mode 100644 index 000000000..4495f0db7 --- /dev/null +++ b/crates/uv-pep440/src/tests.rs @@ -0,0 +1,11 @@ +use super::{Version, VersionSpecifier, VersionSpecifiers}; +use std::str::FromStr; + +#[test] +fn test_version() { + let version = Version::from_str("1.19").unwrap(); + let version_specifier = VersionSpecifier::from_str("== 1.*").unwrap(); + assert!(version_specifier.contains(&version)); + let version_specifiers = VersionSpecifiers::from_str(">=1.16, <2.0").unwrap(); + assert!(version_specifiers.contains(&version)); +} diff --git a/crates/uv-pep440/src/version.rs b/crates/uv-pep440/src/version.rs index 7c9f32540..404791dd1 100644 --- a/crates/uv-pep440/src/version.rs +++ b/crates/uv-pep440/src/version.rs @@ -9,20 +9,12 @@ use std::{ }; /// One of `~=` `==` `!=` `<=` `>=` `<` `>` `===` -#[derive( - Eq, - Ord, - PartialEq, - PartialOrd, - Debug, - Hash, - Clone, - Copy, - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, +#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Hash, Clone, Copy)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize,) )] -#[rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord))] +#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] pub enum Operator { /// `== 1.2.3` Equal, @@ -262,18 +254,26 @@ impl std::fmt::Display for OperatorParseError { /// /// ```rust /// use std::str::FromStr; -/// use uv_pep440::Version; +/// use pep440_rs::Version; /// /// let version = Version::from_str("1.19").unwrap(); /// ``` -#[derive(Clone, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] -#[rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord))] +#[derive(Clone)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize) +)] +#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] pub struct Version { inner: Arc, } -#[derive(Clone, Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] -#[rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord))] +#[derive(Clone, Debug)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize) +)] +#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] enum VersionInner { Small { small: VersionSmall }, Full { full: VersionFull }, @@ -861,8 +861,12 @@ impl FromStr for Version { /// /// Thankfully, such versions are incredibly rare. Virtually all versions have /// zero or one pre, dev or post release components. -#[derive(Clone, Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] -#[rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord))] +#[derive(Clone, Debug)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize) +)] +#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] struct VersionSmall { /// The representation discussed above. repr: u64, @@ -1202,8 +1206,12 @@ impl VersionSmall { /// /// In general, the "full" representation is rarely used in practice since most /// versions will fit into the "small" representation. -#[derive(Clone, Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] -#[rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord))] +#[derive(Clone, Debug)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize) +)] +#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] struct VersionFull { /// The [versioning /// epoch](https://peps.python.org/pep-0440/#version-epochs). Normally @@ -1323,20 +1331,12 @@ impl FromStr for VersionPattern { } /// An optional pre-release modifier and number applied to a version. -#[derive( - PartialEq, - Eq, - Debug, - Hash, - Clone, - Copy, - Ord, - PartialOrd, - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, +#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Ord, PartialOrd)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize,) )] -#[rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord))] +#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] pub struct Prerelease { /// The kind of pre-release. pub kind: PrereleaseKind, @@ -1347,20 +1347,12 @@ pub struct Prerelease { /// Optional pre-release modifier (alpha, beta or release candidate) appended to version /// /// -#[derive( - PartialEq, - Eq, - Debug, - Hash, - Clone, - Copy, - Ord, - PartialOrd, - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, +#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Ord, PartialOrd)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize,) )] -#[rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord))] +#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] pub enum PrereleaseKind { /// alpha pre-release Alpha, @@ -1401,8 +1393,12 @@ impl std::fmt::Display for Prerelease { /// > exactly. /// /// Luckily the default `Ord` implementation for `Vec` matches the PEP 440 rules. -#[derive(Eq, PartialEq, Debug, Clone, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] -#[rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord))] +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize) +)] +#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))] pub enum LocalSegment { /// Not-parseable as integer segment of local version String(String), @@ -2398,1350 +2394,4 @@ pub static MIN_VERSION: LazyLock = LazyLock::new(|| Version::from_str("0a0.dev0").unwrap()); #[cfg(test)] -mod tests { - use std::str::FromStr; - - use crate::VersionSpecifier; - - use super::*; - - /// - #[test] - fn test_packaging_versions() { - let versions = [ - // Implicit epoch of 0 - ("1.0.dev456", Version::new([1, 0]).with_dev(Some(456))), - ( - "1.0a1", - Version::new([1, 0]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 1, - })), - ), - ( - "1.0a2.dev456", - Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 2, - })) - .with_dev(Some(456)), - ), - ( - "1.0a12.dev456", - Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 12, - })) - .with_dev(Some(456)), - ), - ( - "1.0a12", - Version::new([1, 0]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 12, - })), - ), - ( - "1.0b1.dev456", - Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 1, - })) - .with_dev(Some(456)), - ), - ( - "1.0b2", - Version::new([1, 0]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 2, - })), - ), - ( - "1.0b2.post345.dev456", - Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 2, - })) - .with_dev(Some(456)) - .with_post(Some(345)), - ), - ( - "1.0b2.post345", - Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 2, - })) - .with_post(Some(345)), - ), - ( - "1.0b2-346", - Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 2, - })) - .with_post(Some(346)), - ), - ( - "1.0c1.dev456", - Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1, - })) - .with_dev(Some(456)), - ), - ( - "1.0c1", - Version::new([1, 0]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1, - })), - ), - ( - "1.0rc2", - Version::new([1, 0]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 2, - })), - ), - ( - "1.0c3", - Version::new([1, 0]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 3, - })), - ), - ("1.0", Version::new([1, 0])), - ( - "1.0.post456.dev34", - Version::new([1, 0]).with_post(Some(456)).with_dev(Some(34)), - ), - ("1.0.post456", Version::new([1, 0]).with_post(Some(456))), - ("1.1.dev1", Version::new([1, 1]).with_dev(Some(1))), - ( - "1.2+123abc", - Version::new([1, 2]).with_local(vec![LocalSegment::String("123abc".to_string())]), - ), - ( - "1.2+123abc456", - Version::new([1, 2]) - .with_local(vec![LocalSegment::String("123abc456".to_string())]), - ), - ( - "1.2+abc", - Version::new([1, 2]).with_local(vec![LocalSegment::String("abc".to_string())]), - ), - ( - "1.2+abc123", - Version::new([1, 2]).with_local(vec![LocalSegment::String("abc123".to_string())]), - ), - ( - "1.2+abc123def", - Version::new([1, 2]) - .with_local(vec![LocalSegment::String("abc123def".to_string())]), - ), - ( - "1.2+1234.abc", - Version::new([1, 2]).with_local(vec![ - LocalSegment::Number(1234), - LocalSegment::String("abc".to_string()), - ]), - ), - ( - "1.2+123456", - Version::new([1, 2]).with_local(vec![LocalSegment::Number(123_456)]), - ), - ( - "1.2.r32+123456", - Version::new([1, 2]) - .with_post(Some(32)) - .with_local(vec![LocalSegment::Number(123_456)]), - ), - ( - "1.2.rev33+123456", - Version::new([1, 2]) - .with_post(Some(33)) - .with_local(vec![LocalSegment::Number(123_456)]), - ), - // Explicit epoch of 1 - ( - "1!1.0.dev456", - Version::new([1, 0]).with_epoch(1).with_dev(Some(456)), - ), - ( - "1!1.0a1", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 1, - })), - ), - ( - "1!1.0a2.dev456", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 2, - })) - .with_dev(Some(456)), - ), - ( - "1!1.0a12.dev456", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 12, - })) - .with_dev(Some(456)), - ), - ( - "1!1.0a12", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 12, - })), - ), - ( - "1!1.0b1.dev456", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 1, - })) - .with_dev(Some(456)), - ), - ( - "1!1.0b2", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 2, - })), - ), - ( - "1!1.0b2.post345.dev456", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 2, - })) - .with_post(Some(345)) - .with_dev(Some(456)), - ), - ( - "1!1.0b2.post345", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 2, - })) - .with_post(Some(345)), - ), - ( - "1!1.0b2-346", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 2, - })) - .with_post(Some(346)), - ), - ( - "1!1.0c1.dev456", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1, - })) - .with_dev(Some(456)), - ), - ( - "1!1.0c1", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1, - })), - ), - ( - "1!1.0rc2", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 2, - })), - ), - ( - "1!1.0c3", - Version::new([1, 0]) - .with_epoch(1) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 3, - })), - ), - ("1!1.0", Version::new([1, 0]).with_epoch(1)), - ( - "1!1.0.post456.dev34", - Version::new([1, 0]) - .with_epoch(1) - .with_post(Some(456)) - .with_dev(Some(34)), - ), - ( - "1!1.0.post456", - Version::new([1, 0]).with_epoch(1).with_post(Some(456)), - ), - ( - "1!1.1.dev1", - Version::new([1, 1]).with_epoch(1).with_dev(Some(1)), - ), - ( - "1!1.2+123abc", - Version::new([1, 2]) - .with_epoch(1) - .with_local(vec![LocalSegment::String("123abc".to_string())]), - ), - ( - "1!1.2+123abc456", - Version::new([1, 2]) - .with_epoch(1) - .with_local(vec![LocalSegment::String("123abc456".to_string())]), - ), - ( - "1!1.2+abc", - Version::new([1, 2]) - .with_epoch(1) - .with_local(vec![LocalSegment::String("abc".to_string())]), - ), - ( - "1!1.2+abc123", - Version::new([1, 2]) - .with_epoch(1) - .with_local(vec![LocalSegment::String("abc123".to_string())]), - ), - ( - "1!1.2+abc123def", - Version::new([1, 2]) - .with_epoch(1) - .with_local(vec![LocalSegment::String("abc123def".to_string())]), - ), - ( - "1!1.2+1234.abc", - Version::new([1, 2]).with_epoch(1).with_local(vec![ - LocalSegment::Number(1234), - LocalSegment::String("abc".to_string()), - ]), - ), - ( - "1!1.2+123456", - Version::new([1, 2]) - .with_epoch(1) - .with_local(vec![LocalSegment::Number(123_456)]), - ), - ( - "1!1.2.r32+123456", - Version::new([1, 2]) - .with_epoch(1) - .with_post(Some(32)) - .with_local(vec![LocalSegment::Number(123_456)]), - ), - ( - "1!1.2.rev33+123456", - Version::new([1, 2]) - .with_epoch(1) - .with_post(Some(33)) - .with_local(vec![LocalSegment::Number(123_456)]), - ), - ( - "98765!1.2.rev33+123456", - Version::new([1, 2]) - .with_epoch(98765) - .with_post(Some(33)) - .with_local(vec![LocalSegment::Number(123_456)]), - ), - ]; - for (string, structured) in versions { - match Version::from_str(string) { - Err(err) => { - unreachable!( - "expected {string:?} to parse as {structured:?}, but got {err:?}", - structured = structured.as_bloated_debug(), - ) - } - Ok(v) => assert!( - v == structured, - "for {string:?}, expected {structured:?} but got {v:?}", - structured = structured.as_bloated_debug(), - v = v.as_bloated_debug(), - ), - } - let spec = format!("=={string}"); - match VersionSpecifier::from_str(&spec) { - Err(err) => { - unreachable!( - "expected version in {spec:?} to parse as {structured:?}, but got {err:?}", - structured = structured.as_bloated_debug(), - ) - } - Ok(v) => assert!( - v.version() == &structured, - "for {string:?}, expected {structured:?} but got {v:?}", - structured = structured.as_bloated_debug(), - v = v.version.as_bloated_debug(), - ), - } - } - } - - /// - #[test] - fn test_packaging_failures() { - let versions = [ - // Versions with invalid local versions - "1.0+a+", - "1.0++", - "1.0+_foobar", - "1.0+foo&asd", - "1.0+1+1", - // Nonsensical versions should also be invalid - "french toast", - "==french toast", - ]; - for version in versions { - assert!(Version::from_str(version).is_err()); - assert!(VersionSpecifier::from_str(&format!("=={version}")).is_err()); - } - } - - #[test] - fn test_equality_and_normalization() { - let versions = [ - // Various development release incarnations - ("1.0dev", "1.0.dev0"), - ("1.0.dev", "1.0.dev0"), - ("1.0dev1", "1.0.dev1"), - ("1.0dev", "1.0.dev0"), - ("1.0-dev", "1.0.dev0"), - ("1.0-dev1", "1.0.dev1"), - ("1.0DEV", "1.0.dev0"), - ("1.0.DEV", "1.0.dev0"), - ("1.0DEV1", "1.0.dev1"), - ("1.0DEV", "1.0.dev0"), - ("1.0.DEV1", "1.0.dev1"), - ("1.0-DEV", "1.0.dev0"), - ("1.0-DEV1", "1.0.dev1"), - // Various alpha incarnations - ("1.0a", "1.0a0"), - ("1.0.a", "1.0a0"), - ("1.0.a1", "1.0a1"), - ("1.0-a", "1.0a0"), - ("1.0-a1", "1.0a1"), - ("1.0alpha", "1.0a0"), - ("1.0.alpha", "1.0a0"), - ("1.0.alpha1", "1.0a1"), - ("1.0-alpha", "1.0a0"), - ("1.0-alpha1", "1.0a1"), - ("1.0A", "1.0a0"), - ("1.0.A", "1.0a0"), - ("1.0.A1", "1.0a1"), - ("1.0-A", "1.0a0"), - ("1.0-A1", "1.0a1"), - ("1.0ALPHA", "1.0a0"), - ("1.0.ALPHA", "1.0a0"), - ("1.0.ALPHA1", "1.0a1"), - ("1.0-ALPHA", "1.0a0"), - ("1.0-ALPHA1", "1.0a1"), - // Various beta incarnations - ("1.0b", "1.0b0"), - ("1.0.b", "1.0b0"), - ("1.0.b1", "1.0b1"), - ("1.0-b", "1.0b0"), - ("1.0-b1", "1.0b1"), - ("1.0beta", "1.0b0"), - ("1.0.beta", "1.0b0"), - ("1.0.beta1", "1.0b1"), - ("1.0-beta", "1.0b0"), - ("1.0-beta1", "1.0b1"), - ("1.0B", "1.0b0"), - ("1.0.B", "1.0b0"), - ("1.0.B1", "1.0b1"), - ("1.0-B", "1.0b0"), - ("1.0-B1", "1.0b1"), - ("1.0BETA", "1.0b0"), - ("1.0.BETA", "1.0b0"), - ("1.0.BETA1", "1.0b1"), - ("1.0-BETA", "1.0b0"), - ("1.0-BETA1", "1.0b1"), - // Various release candidate incarnations - ("1.0c", "1.0rc0"), - ("1.0.c", "1.0rc0"), - ("1.0.c1", "1.0rc1"), - ("1.0-c", "1.0rc0"), - ("1.0-c1", "1.0rc1"), - ("1.0rc", "1.0rc0"), - ("1.0.rc", "1.0rc0"), - ("1.0.rc1", "1.0rc1"), - ("1.0-rc", "1.0rc0"), - ("1.0-rc1", "1.0rc1"), - ("1.0C", "1.0rc0"), - ("1.0.C", "1.0rc0"), - ("1.0.C1", "1.0rc1"), - ("1.0-C", "1.0rc0"), - ("1.0-C1", "1.0rc1"), - ("1.0RC", "1.0rc0"), - ("1.0.RC", "1.0rc0"), - ("1.0.RC1", "1.0rc1"), - ("1.0-RC", "1.0rc0"), - ("1.0-RC1", "1.0rc1"), - // Various post release incarnations - ("1.0post", "1.0.post0"), - ("1.0.post", "1.0.post0"), - ("1.0post1", "1.0.post1"), - ("1.0post", "1.0.post0"), - ("1.0-post", "1.0.post0"), - ("1.0-post1", "1.0.post1"), - ("1.0POST", "1.0.post0"), - ("1.0.POST", "1.0.post0"), - ("1.0POST1", "1.0.post1"), - ("1.0POST", "1.0.post0"), - ("1.0r", "1.0.post0"), - ("1.0rev", "1.0.post0"), - ("1.0.POST1", "1.0.post1"), - ("1.0.r1", "1.0.post1"), - ("1.0.rev1", "1.0.post1"), - ("1.0-POST", "1.0.post0"), - ("1.0-POST1", "1.0.post1"), - ("1.0-5", "1.0.post5"), - ("1.0-r5", "1.0.post5"), - ("1.0-rev5", "1.0.post5"), - // Local version case insensitivity - ("1.0+AbC", "1.0+abc"), - // Integer Normalization - ("1.01", "1.1"), - ("1.0a05", "1.0a5"), - ("1.0b07", "1.0b7"), - ("1.0c056", "1.0rc56"), - ("1.0rc09", "1.0rc9"), - ("1.0.post000", "1.0.post0"), - ("1.1.dev09000", "1.1.dev9000"), - ("00!1.2", "1.2"), - ("0100!0.0", "100!0.0"), - // Various other normalizations - ("v1.0", "1.0"), - (" v1.0\t\n", "1.0"), - ]; - for (version_str, normalized_str) in versions { - let version = Version::from_str(version_str).unwrap(); - let normalized = Version::from_str(normalized_str).unwrap(); - // Just test version parsing again - assert_eq!(version, normalized, "{version_str} {normalized_str}"); - // Test version normalization - assert_eq!( - version.to_string(), - normalized.to_string(), - "{version_str} {normalized_str}" - ); - } - } - - /// - #[test] - fn test_equality_and_normalization2() { - let versions = [ - ("1.0.dev456", "1.0.dev456"), - ("1.0a1", "1.0a1"), - ("1.0a2.dev456", "1.0a2.dev456"), - ("1.0a12.dev456", "1.0a12.dev456"), - ("1.0a12", "1.0a12"), - ("1.0b1.dev456", "1.0b1.dev456"), - ("1.0b2", "1.0b2"), - ("1.0b2.post345.dev456", "1.0b2.post345.dev456"), - ("1.0b2.post345", "1.0b2.post345"), - ("1.0rc1.dev456", "1.0rc1.dev456"), - ("1.0rc1", "1.0rc1"), - ("1.0", "1.0"), - ("1.0.post456.dev34", "1.0.post456.dev34"), - ("1.0.post456", "1.0.post456"), - ("1.0.1", "1.0.1"), - ("0!1.0.2", "1.0.2"), - ("1.0.3+7", "1.0.3+7"), - ("0!1.0.4+8.0", "1.0.4+8.0"), - ("1.0.5+9.5", "1.0.5+9.5"), - ("1.2+1234.abc", "1.2+1234.abc"), - ("1.2+123456", "1.2+123456"), - ("1.2+123abc", "1.2+123abc"), - ("1.2+123abc456", "1.2+123abc456"), - ("1.2+abc", "1.2+abc"), - ("1.2+abc123", "1.2+abc123"), - ("1.2+abc123def", "1.2+abc123def"), - ("1.1.dev1", "1.1.dev1"), - ("7!1.0.dev456", "7!1.0.dev456"), - ("7!1.0a1", "7!1.0a1"), - ("7!1.0a2.dev456", "7!1.0a2.dev456"), - ("7!1.0a12.dev456", "7!1.0a12.dev456"), - ("7!1.0a12", "7!1.0a12"), - ("7!1.0b1.dev456", "7!1.0b1.dev456"), - ("7!1.0b2", "7!1.0b2"), - ("7!1.0b2.post345.dev456", "7!1.0b2.post345.dev456"), - ("7!1.0b2.post345", "7!1.0b2.post345"), - ("7!1.0rc1.dev456", "7!1.0rc1.dev456"), - ("7!1.0rc1", "7!1.0rc1"), - ("7!1.0", "7!1.0"), - ("7!1.0.post456.dev34", "7!1.0.post456.dev34"), - ("7!1.0.post456", "7!1.0.post456"), - ("7!1.0.1", "7!1.0.1"), - ("7!1.0.2", "7!1.0.2"), - ("7!1.0.3+7", "7!1.0.3+7"), - ("7!1.0.4+8.0", "7!1.0.4+8.0"), - ("7!1.0.5+9.5", "7!1.0.5+9.5"), - ("7!1.1.dev1", "7!1.1.dev1"), - ]; - for (version_str, normalized_str) in versions { - let version = Version::from_str(version_str).unwrap(); - let normalized = Version::from_str(normalized_str).unwrap(); - assert_eq!(version, normalized, "{version_str} {normalized_str}"); - // Test version normalization - assert_eq!( - version.to_string(), - normalized_str, - "{version_str} {normalized_str}" - ); - // Since we're already at it - assert_eq!( - version.to_string(), - normalized.to_string(), - "{version_str} {normalized_str}" - ); - } - } - - #[test] - fn test_star_fixed_version() { - let result = Version::from_str("0.9.1.*"); - assert_eq!(result.unwrap_err(), ErrorKind::Wildcard.into()); - } - - #[test] - fn test_invalid_word() { - let result = Version::from_str("blergh"); - assert_eq!(result.unwrap_err(), ErrorKind::NoLeadingNumber.into()); - } - - #[test] - fn test_from_version_star() { - let p = |s: &str| -> Result { s.parse() }; - assert!(!p("1.2.3").unwrap().is_wildcard()); - assert!(p("1.2.3.*").unwrap().is_wildcard()); - assert_eq!( - p("1.2.*.4.*").unwrap_err(), - PatternErrorKind::WildcardNotTrailing.into(), - ); - assert_eq!( - p("1.0-dev1.*").unwrap_err(), - ErrorKind::UnexpectedEnd { - version: "1.0-dev1".to_string(), - remaining: ".*".to_string() - } - .into(), - ); - assert_eq!( - p("1.0a1.*").unwrap_err(), - ErrorKind::UnexpectedEnd { - version: "1.0a1".to_string(), - remaining: ".*".to_string() - } - .into(), - ); - assert_eq!( - p("1.0.post1.*").unwrap_err(), - ErrorKind::UnexpectedEnd { - version: "1.0.post1".to_string(), - remaining: ".*".to_string() - } - .into(), - ); - assert_eq!( - p("1.0+lolwat.*").unwrap_err(), - ErrorKind::LocalEmpty { precursor: '.' }.into(), - ); - } - - // Tests the valid cases of our version parser. These were written - // in tandem with the parser. - // - // They are meant to be additional (but in some cases likely redundant) - // with some of the above tests. - #[test] - fn parse_version_valid() { - let p = |s: &str| match Parser::new(s.as_bytes()).parse() { - Ok(v) => v, - Err(err) => unreachable!("expected valid version, but got error: {err:?}"), - }; - - // release-only tests - assert_eq!(p("5"), Version::new([5])); - assert_eq!(p("5.6"), Version::new([5, 6])); - assert_eq!(p("5.6.7"), Version::new([5, 6, 7])); - assert_eq!(p("512.623.734"), Version::new([512, 623, 734])); - assert_eq!(p("1.2.3.4"), Version::new([1, 2, 3, 4])); - assert_eq!(p("1.2.3.4.5"), Version::new([1, 2, 3, 4, 5])); - - // epoch tests - assert_eq!(p("4!5"), Version::new([5]).with_epoch(4)); - assert_eq!(p("4!5.6"), Version::new([5, 6]).with_epoch(4)); - - // pre-release tests - assert_eq!( - p("5a1"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 1 - })) - ); - assert_eq!( - p("5alpha1"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 1 - })) - ); - assert_eq!( - p("5b1"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 1 - })) - ); - assert_eq!( - p("5beta1"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Beta, - number: 1 - })) - ); - assert_eq!( - p("5rc1"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1 - })) - ); - assert_eq!( - p("5c1"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1 - })) - ); - assert_eq!( - p("5preview1"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1 - })) - ); - assert_eq!( - p("5pre1"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1 - })) - ); - assert_eq!( - p("5.6.7pre1"), - Version::new([5, 6, 7]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Rc, - number: 1 - })) - ); - assert_eq!( - p("5alpha789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5.alpha789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5-alpha789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5_alpha789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5alpha.789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5alpha-789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5alpha_789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5ALPHA789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5aLpHa789"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 789 - })) - ); - assert_eq!( - p("5alpha"), - Version::new([5]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 0 - })) - ); - - // post-release tests - assert_eq!(p("5post2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5rev2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5r2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5.post2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5-post2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5_post2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5.post.2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5.post-2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5.post_2"), Version::new([5]).with_post(Some(2))); - assert_eq!( - p("5.6.7.post_2"), - Version::new([5, 6, 7]).with_post(Some(2)) - ); - assert_eq!(p("5-2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5.6.7-2"), Version::new([5, 6, 7]).with_post(Some(2))); - assert_eq!(p("5POST2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5PoSt2"), Version::new([5]).with_post(Some(2))); - assert_eq!(p("5post"), Version::new([5]).with_post(Some(0))); - - // dev-release tests - assert_eq!(p("5dev2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5.dev2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5-dev2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5_dev2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5.dev.2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5.dev-2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5.dev_2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5.6.7.dev_2"), Version::new([5, 6, 7]).with_dev(Some(2))); - assert_eq!(p("5DEV2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5dEv2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5DeV2"), Version::new([5]).with_dev(Some(2))); - assert_eq!(p("5dev"), Version::new([5]).with_dev(Some(0))); - - // local tests - assert_eq!( - p("5+2"), - Version::new([5]).with_local(vec![LocalSegment::Number(2)]) - ); - assert_eq!( - p("5+a"), - Version::new([5]).with_local(vec![LocalSegment::String("a".to_string())]) - ); - assert_eq!( - p("5+abc.123"), - Version::new([5]).with_local(vec![ - LocalSegment::String("abc".to_string()), - LocalSegment::Number(123), - ]) - ); - assert_eq!( - p("5+123.abc"), - Version::new([5]).with_local(vec![ - LocalSegment::Number(123), - LocalSegment::String("abc".to_string()), - ]) - ); - assert_eq!( - p("5+18446744073709551615.abc"), - Version::new([5]).with_local(vec![ - LocalSegment::Number(18_446_744_073_709_551_615), - LocalSegment::String("abc".to_string()), - ]) - ); - assert_eq!( - p("5+18446744073709551616.abc"), - Version::new([5]).with_local(vec![ - LocalSegment::String("18446744073709551616".to_string()), - LocalSegment::String("abc".to_string()), - ]) - ); - assert_eq!( - p("5+ABC.123"), - Version::new([5]).with_local(vec![ - LocalSegment::String("abc".to_string()), - LocalSegment::Number(123), - ]) - ); - assert_eq!( - p("5+ABC-123.4_5_xyz-MNO"), - Version::new([5]).with_local(vec![ - LocalSegment::String("abc".to_string()), - LocalSegment::Number(123), - LocalSegment::Number(4), - LocalSegment::Number(5), - LocalSegment::String("xyz".to_string()), - LocalSegment::String("mno".to_string()), - ]) - ); - assert_eq!( - p("5.6.7+abc-00123"), - Version::new([5, 6, 7]).with_local(vec![ - LocalSegment::String("abc".to_string()), - LocalSegment::Number(123), - ]) - ); - assert_eq!( - p("5.6.7+abc-foo00123"), - Version::new([5, 6, 7]).with_local(vec![ - LocalSegment::String("abc".to_string()), - LocalSegment::String("foo00123".to_string()), - ]) - ); - assert_eq!( - p("5.6.7+abc-00123a"), - Version::new([5, 6, 7]).with_local(vec![ - LocalSegment::String("abc".to_string()), - LocalSegment::String("00123a".to_string()), - ]) - ); - - // {pre-release, post-release} tests - assert_eq!( - p("5a2post3"), - Version::new([5]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 2 - })) - .with_post(Some(3)) - ); - assert_eq!( - p("5.a-2_post-3"), - Version::new([5]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 2 - })) - .with_post(Some(3)) - ); - assert_eq!( - p("5a2-3"), - Version::new([5]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 2 - })) - .with_post(Some(3)) - ); - - // Ignoring a no-op 'v' prefix. - assert_eq!(p("v5"), Version::new([5])); - assert_eq!(p("V5"), Version::new([5])); - assert_eq!(p("v5.6.7"), Version::new([5, 6, 7])); - - // Ignoring leading and trailing whitespace. - assert_eq!(p(" v5 "), Version::new([5])); - assert_eq!(p(" 5 "), Version::new([5])); - assert_eq!( - p(" 5.6.7+abc.123.xyz "), - Version::new([5, 6, 7]).with_local(vec![ - LocalSegment::String("abc".to_string()), - LocalSegment::Number(123), - LocalSegment::String("xyz".to_string()) - ]) - ); - assert_eq!(p(" \n5\n \t"), Version::new([5])); - - // min tests - assert!(Parser::new("1.min0".as_bytes()).parse().is_err()); - } - - // Tests the error cases of our version parser. - // - // I wrote these with the intent to cover every possible error - // case. - // - // They are meant to be additional (but in some cases likely redundant) - // with some of the above tests. - #[test] - fn parse_version_invalid() { - let p = |s: &str| match Parser::new(s.as_bytes()).parse() { - Err(err) => err, - Ok(v) => unreachable!( - "expected version parser error, but got: {v:?}", - v = v.as_bloated_debug() - ), - }; - - assert_eq!(p(""), ErrorKind::NoLeadingNumber.into()); - assert_eq!(p("a"), ErrorKind::NoLeadingNumber.into()); - assert_eq!(p("v 5"), ErrorKind::NoLeadingNumber.into()); - assert_eq!(p("V 5"), ErrorKind::NoLeadingNumber.into()); - assert_eq!(p("x 5"), ErrorKind::NoLeadingNumber.into()); - assert_eq!( - p("18446744073709551616"), - ErrorKind::NumberTooBig { - bytes: b"18446744073709551616".to_vec() - } - .into() - ); - assert_eq!(p("5!"), ErrorKind::NoLeadingReleaseNumber.into()); - assert_eq!( - p("5.6./"), - ErrorKind::UnexpectedEnd { - version: "5.6".to_string(), - remaining: "./".to_string() - } - .into() - ); - assert_eq!( - p("5.6.-alpha2"), - ErrorKind::UnexpectedEnd { - version: "5.6".to_string(), - remaining: ".-alpha2".to_string() - } - .into() - ); - assert_eq!( - p("1.2.3a18446744073709551616"), - ErrorKind::NumberTooBig { - bytes: b"18446744073709551616".to_vec() - } - .into() - ); - assert_eq!(p("5+"), ErrorKind::LocalEmpty { precursor: '+' }.into()); - assert_eq!(p("5+ "), ErrorKind::LocalEmpty { precursor: '+' }.into()); - assert_eq!(p("5+abc."), ErrorKind::LocalEmpty { precursor: '.' }.into()); - assert_eq!(p("5+abc-"), ErrorKind::LocalEmpty { precursor: '-' }.into()); - assert_eq!(p("5+abc_"), ErrorKind::LocalEmpty { precursor: '_' }.into()); - assert_eq!( - p("5+abc. "), - ErrorKind::LocalEmpty { precursor: '.' }.into() - ); - assert_eq!( - p("5.6-"), - ErrorKind::UnexpectedEnd { - version: "5.6".to_string(), - remaining: "-".to_string() - } - .into() - ); - } - - #[test] - fn parse_version_pattern_valid() { - let p = |s: &str| match Parser::new(s.as_bytes()).parse_pattern() { - Ok(v) => v, - Err(err) => unreachable!("expected valid version, but got error: {err:?}"), - }; - - assert_eq!(p("5.*"), VersionPattern::wildcard(Version::new([5]))); - assert_eq!(p("5.6.*"), VersionPattern::wildcard(Version::new([5, 6]))); - assert_eq!( - p("2!5.6.*"), - VersionPattern::wildcard(Version::new([5, 6]).with_epoch(2)) - ); - } - - #[test] - fn parse_version_pattern_invalid() { - let p = |s: &str| match Parser::new(s.as_bytes()).parse_pattern() { - Err(err) => err, - Ok(vpat) => unreachable!("expected version pattern parser error, but got: {vpat:?}"), - }; - - assert_eq!(p("*"), ErrorKind::NoLeadingNumber.into()); - assert_eq!(p("2!*"), ErrorKind::NoLeadingReleaseNumber.into()); - } - - // Tests that the ordering between versions is correct. - // - // The ordering example used here was taken from PEP 440: - // https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering - #[test] - fn ordering() { - let versions = &[ - "1.dev0", - "1.0.dev456", - "1.0a1", - "1.0a2.dev456", - "1.0a12.dev456", - "1.0a12", - "1.0b1.dev456", - "1.0b2", - "1.0b2.post345.dev456", - "1.0b2.post345", - "1.0rc1.dev456", - "1.0rc1", - "1.0", - "1.0+abc.5", - "1.0+abc.7", - "1.0+5", - "1.0.post456.dev34", - "1.0.post456", - "1.0.15", - "1.1.dev1", - ]; - for (i, v1) in versions.iter().enumerate() { - for v2 in &versions[i + 1..] { - let less = v1.parse::().unwrap(); - let greater = v2.parse::().unwrap(); - assert_eq!( - less.cmp(&greater), - Ordering::Less, - "less: {:?}\ngreater: {:?}", - less.as_bloated_debug(), - greater.as_bloated_debug() - ); - } - } - } - - #[test] - fn min_version() { - // Ensure that the `.min` suffix precedes all other suffixes. - let less = Version::new([1, 0]).with_min(Some(0)); - - let versions = &[ - "1.dev0", - "1.0.dev456", - "1.0a1", - "1.0a2.dev456", - "1.0a12.dev456", - "1.0a12", - "1.0b1.dev456", - "1.0b2", - "1.0b2.post345.dev456", - "1.0b2.post345", - "1.0rc1.dev456", - "1.0rc1", - "1.0", - "1.0+abc.5", - "1.0+abc.7", - "1.0+5", - "1.0.post456.dev34", - "1.0.post456", - "1.0.15", - "1.1.dev1", - ]; - - for greater in versions { - let greater = greater.parse::().unwrap(); - assert_eq!( - less.cmp(&greater), - Ordering::Less, - "less: {:?}\ngreater: {:?}", - less.as_bloated_debug(), - greater.as_bloated_debug() - ); - } - } - - #[test] - fn max_version() { - // Ensure that the `.max` suffix succeeds all other suffixes. - let greater = Version::new([1, 0]).with_max(Some(0)); - - let versions = &[ - "1.dev0", - "1.0.dev456", - "1.0a1", - "1.0a2.dev456", - "1.0a12.dev456", - "1.0a12", - "1.0b1.dev456", - "1.0b2", - "1.0b2.post345.dev456", - "1.0b2.post345", - "1.0rc1.dev456", - "1.0rc1", - "1.0", - "1.0+abc.5", - "1.0+abc.7", - "1.0+5", - "1.0.post456.dev34", - "1.0.post456", - "1.0", - ]; - - for less in versions { - let less = less.parse::().unwrap(); - assert_eq!( - less.cmp(&greater), - Ordering::Less, - "less: {:?}\ngreater: {:?}", - less.as_bloated_debug(), - greater.as_bloated_debug() - ); - } - - // Ensure that the `.max` suffix plays nicely with pre-release versions. - let greater = Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 1, - })) - .with_max(Some(0)); - - let versions = &["1.0a1", "1.0a1+local", "1.0a1.post1"]; - - for less in versions { - let less = less.parse::().unwrap(); - assert_eq!( - less.cmp(&greater), - Ordering::Less, - "less: {:?}\ngreater: {:?}", - less.as_bloated_debug(), - greater.as_bloated_debug() - ); - } - - // Ensure that the `.max` suffix plays nicely with pre-release versions. - let less = Version::new([1, 0]) - .with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 1, - })) - .with_max(Some(0)); - - let versions = &["1.0b1", "1.0b1+local", "1.0b1.post1", "1.0"]; - - for greater in versions { - let greater = greater.parse::().unwrap(); - assert_eq!( - less.cmp(&greater), - Ordering::Less, - "less: {:?}\ngreater: {:?}", - less.as_bloated_debug(), - greater.as_bloated_debug() - ); - } - } - - // Tests our bespoke u64 decimal integer parser. - #[test] - fn parse_number_u64() { - let p = |s: &str| parse_u64(s.as_bytes()); - assert_eq!(p("0"), Ok(0)); - assert_eq!(p("00"), Ok(0)); - assert_eq!(p("1"), Ok(1)); - assert_eq!(p("01"), Ok(1)); - assert_eq!(p("9"), Ok(9)); - assert_eq!(p("10"), Ok(10)); - assert_eq!(p("18446744073709551615"), Ok(18_446_744_073_709_551_615)); - assert_eq!(p("018446744073709551615"), Ok(18_446_744_073_709_551_615)); - assert_eq!( - p("000000018446744073709551615"), - Ok(18_446_744_073_709_551_615) - ); - - assert_eq!(p("10a"), Err(ErrorKind::InvalidDigit { got: b'a' }.into())); - assert_eq!(p("10["), Err(ErrorKind::InvalidDigit { got: b'[' }.into())); - assert_eq!(p("10/"), Err(ErrorKind::InvalidDigit { got: b'/' }.into())); - assert_eq!( - p("18446744073709551616"), - Err(ErrorKind::NumberTooBig { - bytes: b"18446744073709551616".to_vec() - } - .into()) - ); - assert_eq!( - p("18446744073799551615abc"), - Err(ErrorKind::NumberTooBig { - bytes: b"18446744073799551615abc".to_vec() - } - .into()) - ); - assert_eq!( - parse_u64(b"18446744073799551615\xFF"), - Err(ErrorKind::NumberTooBig { - bytes: b"18446744073799551615\xFF".to_vec() - } - .into()) - ); - } - - /// Wraps a `Version` and provides a more "bloated" debug but standard - /// representation. - /// - /// We don't do this by default because it takes up a ton of space, and - /// just printing out the display version of the version is quite a bit - /// simpler. - /// - /// Nevertheless, when *testing* version parsing, you really want to - /// be able to peek at all of its constituent parts. So we use this in - /// assertion failure messages. - struct VersionBloatedDebug<'a>(&'a Version); - - impl<'a> std::fmt::Debug for VersionBloatedDebug<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Version") - .field("epoch", &self.0.epoch()) - .field("release", &self.0.release()) - .field("pre", &self.0.pre()) - .field("post", &self.0.post()) - .field("dev", &self.0.dev()) - .field("local", &self.0.local()) - .field("min", &self.0.min()) - .field("max", &self.0.max()) - .finish() - } - } - - impl Version { - pub(crate) fn as_bloated_debug(&self) -> impl std::fmt::Debug + '_ { - VersionBloatedDebug(self) - } - } -} +mod tests; diff --git a/crates/uv-pep440/src/version/tests.rs b/crates/uv-pep440/src/version/tests.rs new file mode 100644 index 000000000..54aa2e51f --- /dev/null +++ b/crates/uv-pep440/src/version/tests.rs @@ -0,0 +1,1343 @@ +use std::str::FromStr; + +use crate::VersionSpecifier; + +use super::*; + +/// +#[test] +fn test_packaging_versions() { + let versions = [ + // Implicit epoch of 0 + ("1.0.dev456", Version::new([1, 0]).with_dev(Some(456))), + ( + "1.0a1", + Version::new([1, 0]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 1, + })), + ), + ( + "1.0a2.dev456", + Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 2, + })) + .with_dev(Some(456)), + ), + ( + "1.0a12.dev456", + Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 12, + })) + .with_dev(Some(456)), + ), + ( + "1.0a12", + Version::new([1, 0]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 12, + })), + ), + ( + "1.0b1.dev456", + Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 1, + })) + .with_dev(Some(456)), + ), + ( + "1.0b2", + Version::new([1, 0]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 2, + })), + ), + ( + "1.0b2.post345.dev456", + Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 2, + })) + .with_dev(Some(456)) + .with_post(Some(345)), + ), + ( + "1.0b2.post345", + Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 2, + })) + .with_post(Some(345)), + ), + ( + "1.0b2-346", + Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 2, + })) + .with_post(Some(346)), + ), + ( + "1.0c1.dev456", + Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1, + })) + .with_dev(Some(456)), + ), + ( + "1.0c1", + Version::new([1, 0]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1, + })), + ), + ( + "1.0rc2", + Version::new([1, 0]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 2, + })), + ), + ( + "1.0c3", + Version::new([1, 0]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 3, + })), + ), + ("1.0", Version::new([1, 0])), + ( + "1.0.post456.dev34", + Version::new([1, 0]).with_post(Some(456)).with_dev(Some(34)), + ), + ("1.0.post456", Version::new([1, 0]).with_post(Some(456))), + ("1.1.dev1", Version::new([1, 1]).with_dev(Some(1))), + ( + "1.2+123abc", + Version::new([1, 2]).with_local(vec![LocalSegment::String("123abc".to_string())]), + ), + ( + "1.2+123abc456", + Version::new([1, 2]).with_local(vec![LocalSegment::String("123abc456".to_string())]), + ), + ( + "1.2+abc", + Version::new([1, 2]).with_local(vec![LocalSegment::String("abc".to_string())]), + ), + ( + "1.2+abc123", + Version::new([1, 2]).with_local(vec![LocalSegment::String("abc123".to_string())]), + ), + ( + "1.2+abc123def", + Version::new([1, 2]).with_local(vec![LocalSegment::String("abc123def".to_string())]), + ), + ( + "1.2+1234.abc", + Version::new([1, 2]).with_local(vec![ + LocalSegment::Number(1234), + LocalSegment::String("abc".to_string()), + ]), + ), + ( + "1.2+123456", + Version::new([1, 2]).with_local(vec![LocalSegment::Number(123_456)]), + ), + ( + "1.2.r32+123456", + Version::new([1, 2]) + .with_post(Some(32)) + .with_local(vec![LocalSegment::Number(123_456)]), + ), + ( + "1.2.rev33+123456", + Version::new([1, 2]) + .with_post(Some(33)) + .with_local(vec![LocalSegment::Number(123_456)]), + ), + // Explicit epoch of 1 + ( + "1!1.0.dev456", + Version::new([1, 0]).with_epoch(1).with_dev(Some(456)), + ), + ( + "1!1.0a1", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 1, + })), + ), + ( + "1!1.0a2.dev456", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 2, + })) + .with_dev(Some(456)), + ), + ( + "1!1.0a12.dev456", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 12, + })) + .with_dev(Some(456)), + ), + ( + "1!1.0a12", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 12, + })), + ), + ( + "1!1.0b1.dev456", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 1, + })) + .with_dev(Some(456)), + ), + ( + "1!1.0b2", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 2, + })), + ), + ( + "1!1.0b2.post345.dev456", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 2, + })) + .with_post(Some(345)) + .with_dev(Some(456)), + ), + ( + "1!1.0b2.post345", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 2, + })) + .with_post(Some(345)), + ), + ( + "1!1.0b2-346", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 2, + })) + .with_post(Some(346)), + ), + ( + "1!1.0c1.dev456", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1, + })) + .with_dev(Some(456)), + ), + ( + "1!1.0c1", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1, + })), + ), + ( + "1!1.0rc2", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 2, + })), + ), + ( + "1!1.0c3", + Version::new([1, 0]) + .with_epoch(1) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 3, + })), + ), + ("1!1.0", Version::new([1, 0]).with_epoch(1)), + ( + "1!1.0.post456.dev34", + Version::new([1, 0]) + .with_epoch(1) + .with_post(Some(456)) + .with_dev(Some(34)), + ), + ( + "1!1.0.post456", + Version::new([1, 0]).with_epoch(1).with_post(Some(456)), + ), + ( + "1!1.1.dev1", + Version::new([1, 1]).with_epoch(1).with_dev(Some(1)), + ), + ( + "1!1.2+123abc", + Version::new([1, 2]) + .with_epoch(1) + .with_local(vec![LocalSegment::String("123abc".to_string())]), + ), + ( + "1!1.2+123abc456", + Version::new([1, 2]) + .with_epoch(1) + .with_local(vec![LocalSegment::String("123abc456".to_string())]), + ), + ( + "1!1.2+abc", + Version::new([1, 2]) + .with_epoch(1) + .with_local(vec![LocalSegment::String("abc".to_string())]), + ), + ( + "1!1.2+abc123", + Version::new([1, 2]) + .with_epoch(1) + .with_local(vec![LocalSegment::String("abc123".to_string())]), + ), + ( + "1!1.2+abc123def", + Version::new([1, 2]) + .with_epoch(1) + .with_local(vec![LocalSegment::String("abc123def".to_string())]), + ), + ( + "1!1.2+1234.abc", + Version::new([1, 2]).with_epoch(1).with_local(vec![ + LocalSegment::Number(1234), + LocalSegment::String("abc".to_string()), + ]), + ), + ( + "1!1.2+123456", + Version::new([1, 2]) + .with_epoch(1) + .with_local(vec![LocalSegment::Number(123_456)]), + ), + ( + "1!1.2.r32+123456", + Version::new([1, 2]) + .with_epoch(1) + .with_post(Some(32)) + .with_local(vec![LocalSegment::Number(123_456)]), + ), + ( + "1!1.2.rev33+123456", + Version::new([1, 2]) + .with_epoch(1) + .with_post(Some(33)) + .with_local(vec![LocalSegment::Number(123_456)]), + ), + ( + "98765!1.2.rev33+123456", + Version::new([1, 2]) + .with_epoch(98765) + .with_post(Some(33)) + .with_local(vec![LocalSegment::Number(123_456)]), + ), + ]; + for (string, structured) in versions { + match Version::from_str(string) { + Err(err) => { + unreachable!( + "expected {string:?} to parse as {structured:?}, but got {err:?}", + structured = structured.as_bloated_debug(), + ) + } + Ok(v) => assert!( + v == structured, + "for {string:?}, expected {structured:?} but got {v:?}", + structured = structured.as_bloated_debug(), + v = v.as_bloated_debug(), + ), + } + let spec = format!("=={string}"); + match VersionSpecifier::from_str(&spec) { + Err(err) => { + unreachable!( + "expected version in {spec:?} to parse as {structured:?}, but got {err:?}", + structured = structured.as_bloated_debug(), + ) + } + Ok(v) => assert!( + v.version() == &structured, + "for {string:?}, expected {structured:?} but got {v:?}", + structured = structured.as_bloated_debug(), + v = v.version.as_bloated_debug(), + ), + } + } +} + +/// +#[test] +fn test_packaging_failures() { + let versions = [ + // Versions with invalid local versions + "1.0+a+", + "1.0++", + "1.0+_foobar", + "1.0+foo&asd", + "1.0+1+1", + // Nonsensical versions should also be invalid + "french toast", + "==french toast", + ]; + for version in versions { + assert!(Version::from_str(version).is_err()); + assert!(VersionSpecifier::from_str(&format!("=={version}")).is_err()); + } +} + +#[test] +fn test_equality_and_normalization() { + let versions = [ + // Various development release incarnations + ("1.0dev", "1.0.dev0"), + ("1.0.dev", "1.0.dev0"), + ("1.0dev1", "1.0.dev1"), + ("1.0dev", "1.0.dev0"), + ("1.0-dev", "1.0.dev0"), + ("1.0-dev1", "1.0.dev1"), + ("1.0DEV", "1.0.dev0"), + ("1.0.DEV", "1.0.dev0"), + ("1.0DEV1", "1.0.dev1"), + ("1.0DEV", "1.0.dev0"), + ("1.0.DEV1", "1.0.dev1"), + ("1.0-DEV", "1.0.dev0"), + ("1.0-DEV1", "1.0.dev1"), + // Various alpha incarnations + ("1.0a", "1.0a0"), + ("1.0.a", "1.0a0"), + ("1.0.a1", "1.0a1"), + ("1.0-a", "1.0a0"), + ("1.0-a1", "1.0a1"), + ("1.0alpha", "1.0a0"), + ("1.0.alpha", "1.0a0"), + ("1.0.alpha1", "1.0a1"), + ("1.0-alpha", "1.0a0"), + ("1.0-alpha1", "1.0a1"), + ("1.0A", "1.0a0"), + ("1.0.A", "1.0a0"), + ("1.0.A1", "1.0a1"), + ("1.0-A", "1.0a0"), + ("1.0-A1", "1.0a1"), + ("1.0ALPHA", "1.0a0"), + ("1.0.ALPHA", "1.0a0"), + ("1.0.ALPHA1", "1.0a1"), + ("1.0-ALPHA", "1.0a0"), + ("1.0-ALPHA1", "1.0a1"), + // Various beta incarnations + ("1.0b", "1.0b0"), + ("1.0.b", "1.0b0"), + ("1.0.b1", "1.0b1"), + ("1.0-b", "1.0b0"), + ("1.0-b1", "1.0b1"), + ("1.0beta", "1.0b0"), + ("1.0.beta", "1.0b0"), + ("1.0.beta1", "1.0b1"), + ("1.0-beta", "1.0b0"), + ("1.0-beta1", "1.0b1"), + ("1.0B", "1.0b0"), + ("1.0.B", "1.0b0"), + ("1.0.B1", "1.0b1"), + ("1.0-B", "1.0b0"), + ("1.0-B1", "1.0b1"), + ("1.0BETA", "1.0b0"), + ("1.0.BETA", "1.0b0"), + ("1.0.BETA1", "1.0b1"), + ("1.0-BETA", "1.0b0"), + ("1.0-BETA1", "1.0b1"), + // Various release candidate incarnations + ("1.0c", "1.0rc0"), + ("1.0.c", "1.0rc0"), + ("1.0.c1", "1.0rc1"), + ("1.0-c", "1.0rc0"), + ("1.0-c1", "1.0rc1"), + ("1.0rc", "1.0rc0"), + ("1.0.rc", "1.0rc0"), + ("1.0.rc1", "1.0rc1"), + ("1.0-rc", "1.0rc0"), + ("1.0-rc1", "1.0rc1"), + ("1.0C", "1.0rc0"), + ("1.0.C", "1.0rc0"), + ("1.0.C1", "1.0rc1"), + ("1.0-C", "1.0rc0"), + ("1.0-C1", "1.0rc1"), + ("1.0RC", "1.0rc0"), + ("1.0.RC", "1.0rc0"), + ("1.0.RC1", "1.0rc1"), + ("1.0-RC", "1.0rc0"), + ("1.0-RC1", "1.0rc1"), + // Various post release incarnations + ("1.0post", "1.0.post0"), + ("1.0.post", "1.0.post0"), + ("1.0post1", "1.0.post1"), + ("1.0post", "1.0.post0"), + ("1.0-post", "1.0.post0"), + ("1.0-post1", "1.0.post1"), + ("1.0POST", "1.0.post0"), + ("1.0.POST", "1.0.post0"), + ("1.0POST1", "1.0.post1"), + ("1.0POST", "1.0.post0"), + ("1.0r", "1.0.post0"), + ("1.0rev", "1.0.post0"), + ("1.0.POST1", "1.0.post1"), + ("1.0.r1", "1.0.post1"), + ("1.0.rev1", "1.0.post1"), + ("1.0-POST", "1.0.post0"), + ("1.0-POST1", "1.0.post1"), + ("1.0-5", "1.0.post5"), + ("1.0-r5", "1.0.post5"), + ("1.0-rev5", "1.0.post5"), + // Local version case insensitivity + ("1.0+AbC", "1.0+abc"), + // Integer Normalization + ("1.01", "1.1"), + ("1.0a05", "1.0a5"), + ("1.0b07", "1.0b7"), + ("1.0c056", "1.0rc56"), + ("1.0rc09", "1.0rc9"), + ("1.0.post000", "1.0.post0"), + ("1.1.dev09000", "1.1.dev9000"), + ("00!1.2", "1.2"), + ("0100!0.0", "100!0.0"), + // Various other normalizations + ("v1.0", "1.0"), + (" v1.0\t\n", "1.0"), + ]; + for (version_str, normalized_str) in versions { + let version = Version::from_str(version_str).unwrap(); + let normalized = Version::from_str(normalized_str).unwrap(); + // Just test version parsing again + assert_eq!(version, normalized, "{version_str} {normalized_str}"); + // Test version normalization + assert_eq!( + version.to_string(), + normalized.to_string(), + "{version_str} {normalized_str}" + ); + } +} + +/// +#[test] +fn test_equality_and_normalization2() { + let versions = [ + ("1.0.dev456", "1.0.dev456"), + ("1.0a1", "1.0a1"), + ("1.0a2.dev456", "1.0a2.dev456"), + ("1.0a12.dev456", "1.0a12.dev456"), + ("1.0a12", "1.0a12"), + ("1.0b1.dev456", "1.0b1.dev456"), + ("1.0b2", "1.0b2"), + ("1.0b2.post345.dev456", "1.0b2.post345.dev456"), + ("1.0b2.post345", "1.0b2.post345"), + ("1.0rc1.dev456", "1.0rc1.dev456"), + ("1.0rc1", "1.0rc1"), + ("1.0", "1.0"), + ("1.0.post456.dev34", "1.0.post456.dev34"), + ("1.0.post456", "1.0.post456"), + ("1.0.1", "1.0.1"), + ("0!1.0.2", "1.0.2"), + ("1.0.3+7", "1.0.3+7"), + ("0!1.0.4+8.0", "1.0.4+8.0"), + ("1.0.5+9.5", "1.0.5+9.5"), + ("1.2+1234.abc", "1.2+1234.abc"), + ("1.2+123456", "1.2+123456"), + ("1.2+123abc", "1.2+123abc"), + ("1.2+123abc456", "1.2+123abc456"), + ("1.2+abc", "1.2+abc"), + ("1.2+abc123", "1.2+abc123"), + ("1.2+abc123def", "1.2+abc123def"), + ("1.1.dev1", "1.1.dev1"), + ("7!1.0.dev456", "7!1.0.dev456"), + ("7!1.0a1", "7!1.0a1"), + ("7!1.0a2.dev456", "7!1.0a2.dev456"), + ("7!1.0a12.dev456", "7!1.0a12.dev456"), + ("7!1.0a12", "7!1.0a12"), + ("7!1.0b1.dev456", "7!1.0b1.dev456"), + ("7!1.0b2", "7!1.0b2"), + ("7!1.0b2.post345.dev456", "7!1.0b2.post345.dev456"), + ("7!1.0b2.post345", "7!1.0b2.post345"), + ("7!1.0rc1.dev456", "7!1.0rc1.dev456"), + ("7!1.0rc1", "7!1.0rc1"), + ("7!1.0", "7!1.0"), + ("7!1.0.post456.dev34", "7!1.0.post456.dev34"), + ("7!1.0.post456", "7!1.0.post456"), + ("7!1.0.1", "7!1.0.1"), + ("7!1.0.2", "7!1.0.2"), + ("7!1.0.3+7", "7!1.0.3+7"), + ("7!1.0.4+8.0", "7!1.0.4+8.0"), + ("7!1.0.5+9.5", "7!1.0.5+9.5"), + ("7!1.1.dev1", "7!1.1.dev1"), + ]; + for (version_str, normalized_str) in versions { + let version = Version::from_str(version_str).unwrap(); + let normalized = Version::from_str(normalized_str).unwrap(); + assert_eq!(version, normalized, "{version_str} {normalized_str}"); + // Test version normalization + assert_eq!( + version.to_string(), + normalized_str, + "{version_str} {normalized_str}" + ); + // Since we're already at it + assert_eq!( + version.to_string(), + normalized.to_string(), + "{version_str} {normalized_str}" + ); + } +} + +#[test] +fn test_star_fixed_version() { + let result = Version::from_str("0.9.1.*"); + assert_eq!(result.unwrap_err(), ErrorKind::Wildcard.into()); +} + +#[test] +fn test_invalid_word() { + let result = Version::from_str("blergh"); + assert_eq!(result.unwrap_err(), ErrorKind::NoLeadingNumber.into()); +} + +#[test] +fn test_from_version_star() { + let p = |s: &str| -> Result { s.parse() }; + assert!(!p("1.2.3").unwrap().is_wildcard()); + assert!(p("1.2.3.*").unwrap().is_wildcard()); + assert_eq!( + p("1.2.*.4.*").unwrap_err(), + PatternErrorKind::WildcardNotTrailing.into(), + ); + assert_eq!( + p("1.0-dev1.*").unwrap_err(), + ErrorKind::UnexpectedEnd { + version: "1.0-dev1".to_string(), + remaining: ".*".to_string() + } + .into(), + ); + assert_eq!( + p("1.0a1.*").unwrap_err(), + ErrorKind::UnexpectedEnd { + version: "1.0a1".to_string(), + remaining: ".*".to_string() + } + .into(), + ); + assert_eq!( + p("1.0.post1.*").unwrap_err(), + ErrorKind::UnexpectedEnd { + version: "1.0.post1".to_string(), + remaining: ".*".to_string() + } + .into(), + ); + assert_eq!( + p("1.0+lolwat.*").unwrap_err(), + ErrorKind::LocalEmpty { precursor: '.' }.into(), + ); +} + +// Tests the valid cases of our version parser. These were written +// in tandem with the parser. +// +// They are meant to be additional (but in some cases likely redundant) +// with some of the above tests. +#[test] +fn parse_version_valid() { + let p = |s: &str| match Parser::new(s.as_bytes()).parse() { + Ok(v) => v, + Err(err) => unreachable!("expected valid version, but got error: {err:?}"), + }; + + // release-only tests + assert_eq!(p("5"), Version::new([5])); + assert_eq!(p("5.6"), Version::new([5, 6])); + assert_eq!(p("5.6.7"), Version::new([5, 6, 7])); + assert_eq!(p("512.623.734"), Version::new([512, 623, 734])); + assert_eq!(p("1.2.3.4"), Version::new([1, 2, 3, 4])); + assert_eq!(p("1.2.3.4.5"), Version::new([1, 2, 3, 4, 5])); + + // epoch tests + assert_eq!(p("4!5"), Version::new([5]).with_epoch(4)); + assert_eq!(p("4!5.6"), Version::new([5, 6]).with_epoch(4)); + + // pre-release tests + assert_eq!( + p("5a1"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 1 + })) + ); + assert_eq!( + p("5alpha1"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 1 + })) + ); + assert_eq!( + p("5b1"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 1 + })) + ); + assert_eq!( + p("5beta1"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Beta, + number: 1 + })) + ); + assert_eq!( + p("5rc1"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1 + })) + ); + assert_eq!( + p("5c1"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1 + })) + ); + assert_eq!( + p("5preview1"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1 + })) + ); + assert_eq!( + p("5pre1"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1 + })) + ); + assert_eq!( + p("5.6.7pre1"), + Version::new([5, 6, 7]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Rc, + number: 1 + })) + ); + assert_eq!( + p("5alpha789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5.alpha789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5-alpha789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5_alpha789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5alpha.789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5alpha-789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5alpha_789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5ALPHA789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5aLpHa789"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 789 + })) + ); + assert_eq!( + p("5alpha"), + Version::new([5]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 0 + })) + ); + + // post-release tests + assert_eq!(p("5post2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5rev2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5r2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5.post2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5-post2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5_post2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5.post.2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5.post-2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5.post_2"), Version::new([5]).with_post(Some(2))); + assert_eq!( + p("5.6.7.post_2"), + Version::new([5, 6, 7]).with_post(Some(2)) + ); + assert_eq!(p("5-2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5.6.7-2"), Version::new([5, 6, 7]).with_post(Some(2))); + assert_eq!(p("5POST2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5PoSt2"), Version::new([5]).with_post(Some(2))); + assert_eq!(p("5post"), Version::new([5]).with_post(Some(0))); + + // dev-release tests + assert_eq!(p("5dev2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5.dev2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5-dev2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5_dev2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5.dev.2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5.dev-2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5.dev_2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5.6.7.dev_2"), Version::new([5, 6, 7]).with_dev(Some(2))); + assert_eq!(p("5DEV2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5dEv2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5DeV2"), Version::new([5]).with_dev(Some(2))); + assert_eq!(p("5dev"), Version::new([5]).with_dev(Some(0))); + + // local tests + assert_eq!( + p("5+2"), + Version::new([5]).with_local(vec![LocalSegment::Number(2)]) + ); + assert_eq!( + p("5+a"), + Version::new([5]).with_local(vec![LocalSegment::String("a".to_string())]) + ); + assert_eq!( + p("5+abc.123"), + Version::new([5]).with_local(vec![ + LocalSegment::String("abc".to_string()), + LocalSegment::Number(123), + ]) + ); + assert_eq!( + p("5+123.abc"), + Version::new([5]).with_local(vec![ + LocalSegment::Number(123), + LocalSegment::String("abc".to_string()), + ]) + ); + assert_eq!( + p("5+18446744073709551615.abc"), + Version::new([5]).with_local(vec![ + LocalSegment::Number(18_446_744_073_709_551_615), + LocalSegment::String("abc".to_string()), + ]) + ); + assert_eq!( + p("5+18446744073709551616.abc"), + Version::new([5]).with_local(vec![ + LocalSegment::String("18446744073709551616".to_string()), + LocalSegment::String("abc".to_string()), + ]) + ); + assert_eq!( + p("5+ABC.123"), + Version::new([5]).with_local(vec![ + LocalSegment::String("abc".to_string()), + LocalSegment::Number(123), + ]) + ); + assert_eq!( + p("5+ABC-123.4_5_xyz-MNO"), + Version::new([5]).with_local(vec![ + LocalSegment::String("abc".to_string()), + LocalSegment::Number(123), + LocalSegment::Number(4), + LocalSegment::Number(5), + LocalSegment::String("xyz".to_string()), + LocalSegment::String("mno".to_string()), + ]) + ); + assert_eq!( + p("5.6.7+abc-00123"), + Version::new([5, 6, 7]).with_local(vec![ + LocalSegment::String("abc".to_string()), + LocalSegment::Number(123), + ]) + ); + assert_eq!( + p("5.6.7+abc-foo00123"), + Version::new([5, 6, 7]).with_local(vec![ + LocalSegment::String("abc".to_string()), + LocalSegment::String("foo00123".to_string()), + ]) + ); + assert_eq!( + p("5.6.7+abc-00123a"), + Version::new([5, 6, 7]).with_local(vec![ + LocalSegment::String("abc".to_string()), + LocalSegment::String("00123a".to_string()), + ]) + ); + + // {pre-release, post-release} tests + assert_eq!( + p("5a2post3"), + Version::new([5]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 2 + })) + .with_post(Some(3)) + ); + assert_eq!( + p("5.a-2_post-3"), + Version::new([5]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 2 + })) + .with_post(Some(3)) + ); + assert_eq!( + p("5a2-3"), + Version::new([5]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 2 + })) + .with_post(Some(3)) + ); + + // Ignoring a no-op 'v' prefix. + assert_eq!(p("v5"), Version::new([5])); + assert_eq!(p("V5"), Version::new([5])); + assert_eq!(p("v5.6.7"), Version::new([5, 6, 7])); + + // Ignoring leading and trailing whitespace. + assert_eq!(p(" v5 "), Version::new([5])); + assert_eq!(p(" 5 "), Version::new([5])); + assert_eq!( + p(" 5.6.7+abc.123.xyz "), + Version::new([5, 6, 7]).with_local(vec![ + LocalSegment::String("abc".to_string()), + LocalSegment::Number(123), + LocalSegment::String("xyz".to_string()) + ]) + ); + assert_eq!(p(" \n5\n \t"), Version::new([5])); + + // min tests + assert!(Parser::new("1.min0".as_bytes()).parse().is_err()); +} + +// Tests the error cases of our version parser. +// +// I wrote these with the intent to cover every possible error +// case. +// +// They are meant to be additional (but in some cases likely redundant) +// with some of the above tests. +#[test] +fn parse_version_invalid() { + let p = |s: &str| match Parser::new(s.as_bytes()).parse() { + Err(err) => err, + Ok(v) => unreachable!( + "expected version parser error, but got: {v:?}", + v = v.as_bloated_debug() + ), + }; + + assert_eq!(p(""), ErrorKind::NoLeadingNumber.into()); + assert_eq!(p("a"), ErrorKind::NoLeadingNumber.into()); + assert_eq!(p("v 5"), ErrorKind::NoLeadingNumber.into()); + assert_eq!(p("V 5"), ErrorKind::NoLeadingNumber.into()); + assert_eq!(p("x 5"), ErrorKind::NoLeadingNumber.into()); + assert_eq!( + p("18446744073709551616"), + ErrorKind::NumberTooBig { + bytes: b"18446744073709551616".to_vec() + } + .into() + ); + assert_eq!(p("5!"), ErrorKind::NoLeadingReleaseNumber.into()); + assert_eq!( + p("5.6./"), + ErrorKind::UnexpectedEnd { + version: "5.6".to_string(), + remaining: "./".to_string() + } + .into() + ); + assert_eq!( + p("5.6.-alpha2"), + ErrorKind::UnexpectedEnd { + version: "5.6".to_string(), + remaining: ".-alpha2".to_string() + } + .into() + ); + assert_eq!( + p("1.2.3a18446744073709551616"), + ErrorKind::NumberTooBig { + bytes: b"18446744073709551616".to_vec() + } + .into() + ); + assert_eq!(p("5+"), ErrorKind::LocalEmpty { precursor: '+' }.into()); + assert_eq!(p("5+ "), ErrorKind::LocalEmpty { precursor: '+' }.into()); + assert_eq!(p("5+abc."), ErrorKind::LocalEmpty { precursor: '.' }.into()); + assert_eq!(p("5+abc-"), ErrorKind::LocalEmpty { precursor: '-' }.into()); + assert_eq!(p("5+abc_"), ErrorKind::LocalEmpty { precursor: '_' }.into()); + assert_eq!( + p("5+abc. "), + ErrorKind::LocalEmpty { precursor: '.' }.into() + ); + assert_eq!( + p("5.6-"), + ErrorKind::UnexpectedEnd { + version: "5.6".to_string(), + remaining: "-".to_string() + } + .into() + ); +} + +#[test] +fn parse_version_pattern_valid() { + let p = |s: &str| match Parser::new(s.as_bytes()).parse_pattern() { + Ok(v) => v, + Err(err) => unreachable!("expected valid version, but got error: {err:?}"), + }; + + assert_eq!(p("5.*"), VersionPattern::wildcard(Version::new([5]))); + assert_eq!(p("5.6.*"), VersionPattern::wildcard(Version::new([5, 6]))); + assert_eq!( + p("2!5.6.*"), + VersionPattern::wildcard(Version::new([5, 6]).with_epoch(2)) + ); +} + +#[test] +fn parse_version_pattern_invalid() { + let p = |s: &str| match Parser::new(s.as_bytes()).parse_pattern() { + Err(err) => err, + Ok(vpat) => unreachable!("expected version pattern parser error, but got: {vpat:?}"), + }; + + assert_eq!(p("*"), ErrorKind::NoLeadingNumber.into()); + assert_eq!(p("2!*"), ErrorKind::NoLeadingReleaseNumber.into()); +} + +// Tests that the ordering between versions is correct. +// +// The ordering example used here was taken from PEP 440: +// https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering +#[test] +fn ordering() { + let versions = &[ + "1.dev0", + "1.0.dev456", + "1.0a1", + "1.0a2.dev456", + "1.0a12.dev456", + "1.0a12", + "1.0b1.dev456", + "1.0b2", + "1.0b2.post345.dev456", + "1.0b2.post345", + "1.0rc1.dev456", + "1.0rc1", + "1.0", + "1.0+abc.5", + "1.0+abc.7", + "1.0+5", + "1.0.post456.dev34", + "1.0.post456", + "1.0.15", + "1.1.dev1", + ]; + for (i, v1) in versions.iter().enumerate() { + for v2 in &versions[i + 1..] { + let less = v1.parse::().unwrap(); + let greater = v2.parse::().unwrap(); + assert_eq!( + less.cmp(&greater), + Ordering::Less, + "less: {:?}\ngreater: {:?}", + less.as_bloated_debug(), + greater.as_bloated_debug() + ); + } + } +} + +#[test] +fn min_version() { + // Ensure that the `.min` suffix precedes all other suffixes. + let less = Version::new([1, 0]).with_min(Some(0)); + + let versions = &[ + "1.dev0", + "1.0.dev456", + "1.0a1", + "1.0a2.dev456", + "1.0a12.dev456", + "1.0a12", + "1.0b1.dev456", + "1.0b2", + "1.0b2.post345.dev456", + "1.0b2.post345", + "1.0rc1.dev456", + "1.0rc1", + "1.0", + "1.0+abc.5", + "1.0+abc.7", + "1.0+5", + "1.0.post456.dev34", + "1.0.post456", + "1.0.15", + "1.1.dev1", + ]; + + for greater in versions { + let greater = greater.parse::().unwrap(); + assert_eq!( + less.cmp(&greater), + Ordering::Less, + "less: {:?}\ngreater: {:?}", + less.as_bloated_debug(), + greater.as_bloated_debug() + ); + } +} + +#[test] +fn max_version() { + // Ensure that the `.max` suffix succeeds all other suffixes. + let greater = Version::new([1, 0]).with_max(Some(0)); + + let versions = &[ + "1.dev0", + "1.0.dev456", + "1.0a1", + "1.0a2.dev456", + "1.0a12.dev456", + "1.0a12", + "1.0b1.dev456", + "1.0b2", + "1.0b2.post345.dev456", + "1.0b2.post345", + "1.0rc1.dev456", + "1.0rc1", + "1.0", + "1.0+abc.5", + "1.0+abc.7", + "1.0+5", + "1.0.post456.dev34", + "1.0.post456", + "1.0", + ]; + + for less in versions { + let less = less.parse::().unwrap(); + assert_eq!( + less.cmp(&greater), + Ordering::Less, + "less: {:?}\ngreater: {:?}", + less.as_bloated_debug(), + greater.as_bloated_debug() + ); + } + + // Ensure that the `.max` suffix plays nicely with pre-release versions. + let greater = Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 1, + })) + .with_max(Some(0)); + + let versions = &["1.0a1", "1.0a1+local", "1.0a1.post1"]; + + for less in versions { + let less = less.parse::().unwrap(); + assert_eq!( + less.cmp(&greater), + Ordering::Less, + "less: {:?}\ngreater: {:?}", + less.as_bloated_debug(), + greater.as_bloated_debug() + ); + } + + // Ensure that the `.max` suffix plays nicely with pre-release versions. + let less = Version::new([1, 0]) + .with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 1, + })) + .with_max(Some(0)); + + let versions = &["1.0b1", "1.0b1+local", "1.0b1.post1", "1.0"]; + + for greater in versions { + let greater = greater.parse::().unwrap(); + assert_eq!( + less.cmp(&greater), + Ordering::Less, + "less: {:?}\ngreater: {:?}", + less.as_bloated_debug(), + greater.as_bloated_debug() + ); + } +} + +// Tests our bespoke u64 decimal integer parser. +#[test] +fn parse_number_u64() { + let p = |s: &str| parse_u64(s.as_bytes()); + assert_eq!(p("0"), Ok(0)); + assert_eq!(p("00"), Ok(0)); + assert_eq!(p("1"), Ok(1)); + assert_eq!(p("01"), Ok(1)); + assert_eq!(p("9"), Ok(9)); + assert_eq!(p("10"), Ok(10)); + assert_eq!(p("18446744073709551615"), Ok(18_446_744_073_709_551_615)); + assert_eq!(p("018446744073709551615"), Ok(18_446_744_073_709_551_615)); + assert_eq!( + p("000000018446744073709551615"), + Ok(18_446_744_073_709_551_615) + ); + + assert_eq!(p("10a"), Err(ErrorKind::InvalidDigit { got: b'a' }.into())); + assert_eq!(p("10["), Err(ErrorKind::InvalidDigit { got: b'[' }.into())); + assert_eq!(p("10/"), Err(ErrorKind::InvalidDigit { got: b'/' }.into())); + assert_eq!( + p("18446744073709551616"), + Err(ErrorKind::NumberTooBig { + bytes: b"18446744073709551616".to_vec() + } + .into()) + ); + assert_eq!( + p("18446744073799551615abc"), + Err(ErrorKind::NumberTooBig { + bytes: b"18446744073799551615abc".to_vec() + } + .into()) + ); + assert_eq!( + parse_u64(b"18446744073799551615\xFF"), + Err(ErrorKind::NumberTooBig { + bytes: b"18446744073799551615\xFF".to_vec() + } + .into()) + ); +} + +/// Wraps a `Version` and provides a more "bloated" debug but standard +/// representation. +/// +/// We don't do this by default because it takes up a ton of space, and +/// just printing out the display version of the version is quite a bit +/// simpler. +/// +/// Nevertheless, when *testing* version parsing, you really want to +/// be able to peek at all of its constituent parts. So we use this in +/// assertion failure messages. +struct VersionBloatedDebug<'a>(&'a Version); + +impl<'a> std::fmt::Debug for VersionBloatedDebug<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Version") + .field("epoch", &self.0.epoch()) + .field("release", &self.0.release()) + .field("pre", &self.0.pre()) + .field("post", &self.0.post()) + .field("dev", &self.0.dev()) + .field("local", &self.0.local()) + .field("min", &self.0.min()) + .field("max", &self.0.max()) + .finish() + } +} + +impl Version { + pub(crate) fn as_bloated_debug(&self) -> impl std::fmt::Debug + '_ { + VersionBloatedDebug(self) + } +} diff --git a/crates/uv-pep440/src/version_specifier.rs b/crates/uv-pep440/src/version_specifier.rs index b8785e653..56fa5f597 100644 --- a/crates/uv-pep440/src/version_specifier.rs +++ b/crates/uv-pep440/src/version_specifier.rs @@ -2,11 +2,11 @@ use std::cmp::Ordering; use std::ops::Bound; use std::str::FromStr; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; - use crate::{ version, Operator, OperatorParseError, Version, VersionPattern, VersionPatternParseError, }; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use tracing::warn; /// Sorted version specifiers, such as `>=2.1,<3`. /// @@ -69,6 +69,46 @@ impl VersionSpecifiers { specifiers.sort_by(|a, b| a.version().cmp(b.version())); Self(specifiers) } + + /// Returns the [`VersionSpecifiers`] whose union represents the given range. + /// + /// This function is not applicable to ranges involving pre-release versions. + pub fn from_release_only_bounds<'a>( + mut bounds: impl Iterator, &'a Bound)>, + ) -> Self { + let mut specifiers = Vec::new(); + + let Some((start, mut next)) = bounds.next() else { + return Self::empty(); + }; + + // Add specifiers for the holes between the bounds. + for (lower, upper) in bounds { + match (next, lower) { + // Ex) [3.7, 3.8.5), (3.8.5, 3.9] -> >=3.7,!=3.8.5,<=3.9 + (Bound::Excluded(prev), Bound::Excluded(lower)) if prev == lower => { + specifiers.push(VersionSpecifier::not_equals_version(prev.clone())); + } + // Ex) [3.7, 3.8), (3.8, 3.9] -> >=3.7,!=3.8.*,<=3.9 + (Bound::Excluded(prev), Bound::Included(lower)) + if prev.release().len() == 2 + && lower.release() == [prev.release()[0], prev.release()[1] + 1] => + { + specifiers.push(VersionSpecifier::not_equals_star_version(prev.clone())); + } + _ => { + warn!("Ignoring unsupported gap in `requires-python` version: {next:?} -> {lower:?}"); + } + } + next = upper; + } + let end = next; + + // Add the specifiers for the bounding range. + specifiers.extend(VersionSpecifier::from_release_only_bounds((start, end))); + + Self::from_unsorted(specifiers) + } } impl FromIterator for VersionSpecifiers { @@ -188,7 +228,7 @@ impl VersionSpecifiersParseError { impl std::error::Error for VersionSpecifiersParseError {} -/// A version range such such as `>1.2.3`, `<=4!5.6.7-a8.post9.dev0` or `== 4.1.*`. Parse with +/// A version range such as `>1.2.3`, `<=4!5.6.7-a8.post9.dev0` or `== 4.1.*`. Parse with /// `VersionSpecifier::from_str` /// /// ```rust @@ -762,959 +802,4 @@ pub(crate) fn parse_version_specifiers( } #[cfg(test)] -mod tests { - use std::{cmp::Ordering, str::FromStr}; - - use indoc::indoc; - - use crate::LocalSegment; - - use super::*; - - /// - #[test] - fn test_equal() { - let version = Version::from_str("1.1.post1").unwrap(); - - assert!(!VersionSpecifier::from_str("== 1.1") - .unwrap() - .contains(&version)); - assert!(VersionSpecifier::from_str("== 1.1.post1") - .unwrap() - .contains(&version)); - assert!(VersionSpecifier::from_str("== 1.1.*") - .unwrap() - .contains(&version)); - } - - const VERSIONS_ALL: &[&str] = &[ - // Implicit epoch of 0 - "1.0.dev456", - "1.0a1", - "1.0a2.dev456", - "1.0a12.dev456", - "1.0a12", - "1.0b1.dev456", - "1.0b2", - "1.0b2.post345.dev456", - "1.0b2.post345", - "1.0b2-346", - "1.0c1.dev456", - "1.0c1", - "1.0rc2", - "1.0c3", - "1.0", - "1.0.post456.dev34", - "1.0.post456", - "1.1.dev1", - "1.2+123abc", - "1.2+123abc456", - "1.2+abc", - "1.2+abc123", - "1.2+abc123def", - "1.2+1234.abc", - "1.2+123456", - "1.2.r32+123456", - "1.2.rev33+123456", - // Explicit epoch of 1 - "1!1.0.dev456", - "1!1.0a1", - "1!1.0a2.dev456", - "1!1.0a12.dev456", - "1!1.0a12", - "1!1.0b1.dev456", - "1!1.0b2", - "1!1.0b2.post345.dev456", - "1!1.0b2.post345", - "1!1.0b2-346", - "1!1.0c1.dev456", - "1!1.0c1", - "1!1.0rc2", - "1!1.0c3", - "1!1.0", - "1!1.0.post456.dev34", - "1!1.0.post456", - "1!1.1.dev1", - "1!1.2+123abc", - "1!1.2+123abc456", - "1!1.2+abc", - "1!1.2+abc123", - "1!1.2+abc123def", - "1!1.2+1234.abc", - "1!1.2+123456", - "1!1.2.r32+123456", - "1!1.2.rev33+123456", - ]; - - /// - /// - /// - /// These tests are a lot shorter than the pypa/packaging version since we implement all - /// comparisons through one method - #[test] - fn test_operators_true() { - let versions: Vec = VERSIONS_ALL - .iter() - .map(|version| Version::from_str(version).unwrap()) - .collect(); - - // Below we'll generate every possible combination of VERSIONS_ALL that - // should be true for the given operator - let operations = [ - // Verify that the less than (<) operator works correctly - versions - .iter() - .enumerate() - .flat_map(|(i, x)| { - versions[i + 1..] - .iter() - .map(move |y| (x, y, Ordering::Less)) - }) - .collect::>(), - // Verify that the equal (==) operator works correctly - versions - .iter() - .map(move |x| (x, x, Ordering::Equal)) - .collect::>(), - // Verify that the greater than (>) operator works correctly - versions - .iter() - .enumerate() - .flat_map(|(i, x)| versions[..i].iter().map(move |y| (x, y, Ordering::Greater))) - .collect::>(), - ] - .into_iter() - .flatten(); - - for (a, b, ordering) in operations { - assert_eq!(a.cmp(b), ordering, "{a} {ordering:?} {b}"); - } - } - - const VERSIONS_0: &[&str] = &[ - "1.0.dev456", - "1.0a1", - "1.0a2.dev456", - "1.0a12.dev456", - "1.0a12", - "1.0b1.dev456", - "1.0b2", - "1.0b2.post345.dev456", - "1.0b2.post345", - "1.0b2-346", - "1.0c1.dev456", - "1.0c1", - "1.0rc2", - "1.0c3", - "1.0", - "1.0.post456.dev34", - "1.0.post456", - "1.1.dev1", - "1.2+123abc", - "1.2+123abc456", - "1.2+abc", - "1.2+abc123", - "1.2+abc123def", - "1.2+1234.abc", - "1.2+123456", - "1.2.r32+123456", - "1.2.rev33+123456", - ]; - - const SPECIFIERS_OTHER: &[&str] = &[ - "== 1.*", "== 1.0.*", "== 1.1.*", "== 1.2.*", "== 2.*", "~= 1.0", "~= 1.0b1", "~= 1.1", - "~= 1.2", "~= 2.0", - ]; - - const EXPECTED_OTHER: &[[bool; 10]] = &[ - [ - true, true, false, false, false, false, false, false, false, false, - ], - [ - true, true, false, false, false, false, false, false, false, false, - ], - [ - true, true, false, false, false, false, false, false, false, false, - ], - [ - true, true, false, false, false, false, false, false, false, false, - ], - [ - true, true, false, false, false, false, false, false, false, false, - ], - [ - true, true, false, false, false, false, false, false, false, false, - ], - [ - true, true, false, false, false, false, true, false, false, false, - ], - [ - true, true, false, false, false, false, true, false, false, false, - ], - [ - true, true, false, false, false, false, true, false, false, false, - ], - [ - true, true, false, false, false, false, true, false, false, false, - ], - [ - true, true, false, false, false, false, true, false, false, false, - ], - [ - true, true, false, false, false, false, true, false, false, false, - ], - [ - true, true, false, false, false, false, true, false, false, false, - ], - [ - true, true, false, false, false, false, true, false, false, false, - ], - [ - true, true, false, false, false, true, true, false, false, false, - ], - [ - true, true, false, false, false, true, true, false, false, false, - ], - [ - true, true, false, false, false, true, true, false, false, false, - ], - [ - true, false, true, false, false, true, true, false, false, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - [ - true, false, false, true, false, true, true, true, true, false, - ], - ]; - - /// Test for tilde equal (~=) and star equal (== x.y.*) recorded from pypa/packaging - /// - /// Well, except for - #[test] - fn test_operators_other() { - let versions = VERSIONS_0 - .iter() - .map(|version| Version::from_str(version).unwrap()); - let specifiers: Vec<_> = SPECIFIERS_OTHER - .iter() - .map(|specifier| VersionSpecifier::from_str(specifier).unwrap()) - .collect(); - - for (version, expected) in versions.zip(EXPECTED_OTHER) { - let actual = specifiers - .iter() - .map(|specifier| specifier.contains(&version)); - for ((actual, expected), _specifier) in actual.zip(expected).zip(SPECIFIERS_OTHER) { - assert_eq!(actual, *expected); - } - } - } - - #[test] - fn test_arbitrary_equality() { - assert!(VersionSpecifier::from_str("=== 1.2a1") - .unwrap() - .contains(&Version::from_str("1.2a1").unwrap())); - assert!(!VersionSpecifier::from_str("=== 1.2a1") - .unwrap() - .contains(&Version::from_str("1.2a1+local").unwrap())); - } - - #[test] - fn test_specifiers_true() { - let pairs = [ - // Test the equality operation - ("2.0", "==2"), - ("2.0", "==2.0"), - ("2.0", "==2.0.0"), - ("2.0+deadbeef", "==2"), - ("2.0+deadbeef", "==2.0"), - ("2.0+deadbeef", "==2.0.0"), - ("2.0+deadbeef", "==2+deadbeef"), - ("2.0+deadbeef", "==2.0+deadbeef"), - ("2.0+deadbeef", "==2.0.0+deadbeef"), - ("2.0+deadbeef.0", "==2.0.0+deadbeef.00"), - // Test the equality operation with a prefix - ("2.dev1", "==2.*"), - ("2a1", "==2.*"), - ("2a1.post1", "==2.*"), - ("2b1", "==2.*"), - ("2b1.dev1", "==2.*"), - ("2c1", "==2.*"), - ("2c1.post1.dev1", "==2.*"), - ("2c1.post1.dev1", "==2.0.*"), - ("2rc1", "==2.*"), - ("2rc1", "==2.0.*"), - ("2", "==2.*"), - ("2", "==2.0.*"), - ("2", "==0!2.*"), - ("0!2", "==2.*"), - ("2.0", "==2.*"), - ("2.0.0", "==2.*"), - ("2.1+local.version", "==2.1.*"), - // Test the in-equality operation - ("2.1", "!=2"), - ("2.1", "!=2.0"), - ("2.0.1", "!=2"), - ("2.0.1", "!=2.0"), - ("2.0.1", "!=2.0.0"), - ("2.0", "!=2.0+deadbeef"), - // Test the in-equality operation with a prefix - ("2.0", "!=3.*"), - ("2.1", "!=2.0.*"), - // Test the greater than equal operation - ("2.0", ">=2"), - ("2.0", ">=2.0"), - ("2.0", ">=2.0.0"), - ("2.0.post1", ">=2"), - ("2.0.post1.dev1", ">=2"), - ("3", ">=2"), - // Test the less than equal operation - ("2.0", "<=2"), - ("2.0", "<=2.0"), - ("2.0", "<=2.0.0"), - ("2.0.dev1", "<=2"), - ("2.0a1", "<=2"), - ("2.0a1.dev1", "<=2"), - ("2.0b1", "<=2"), - ("2.0b1.post1", "<=2"), - ("2.0c1", "<=2"), - ("2.0c1.post1.dev1", "<=2"), - ("2.0rc1", "<=2"), - ("1", "<=2"), - // Test the greater than operation - ("3", ">2"), - ("2.1", ">2.0"), - ("2.0.1", ">2"), - ("2.1.post1", ">2"), - ("2.1+local.version", ">2"), - // Test the less than operation - ("1", "<2"), - ("2.0", "<2.1"), - ("2.0.dev0", "<2.1"), - // Test the compatibility operation - ("1", "~=1.0"), - ("1.0.1", "~=1.0"), - ("1.1", "~=1.0"), - ("1.9999999", "~=1.0"), - ("1.1", "~=1.0a1"), - ("2022.01.01", "~=2022.01.01"), - // Test that epochs are handled sanely - ("2!1.0", "~=2!1.0"), - ("2!1.0", "==2!1.*"), - ("2!1.0", "==2!1.0"), - ("2!1.0", "!=1.0"), - ("1.0", "!=2!1.0"), - ("1.0", "<=2!0.1"), - ("2!1.0", ">=2.0"), - ("1.0", "<2!0.1"), - ("2!1.0", ">2.0"), - // Test some normalization rules - ("2.0.5", ">2.0dev"), - ]; - - for (s_version, s_spec) in pairs { - let version = s_version.parse::().unwrap(); - let spec = s_spec.parse::().unwrap(); - assert!( - spec.contains(&version), - "{s_version} {s_spec}\nversion repr: {:?}\nspec version repr: {:?}", - version.as_bloated_debug(), - spec.version.as_bloated_debug(), - ); - } - } - - #[test] - fn test_specifier_false() { - let pairs = [ - // Test the equality operation - ("2.1", "==2"), - ("2.1", "==2.0"), - ("2.1", "==2.0.0"), - ("2.0", "==2.0+deadbeef"), - // Test the equality operation with a prefix - ("2.0", "==3.*"), - ("2.1", "==2.0.*"), - // Test the in-equality operation - ("2.0", "!=2"), - ("2.0", "!=2.0"), - ("2.0", "!=2.0.0"), - ("2.0+deadbeef", "!=2"), - ("2.0+deadbeef", "!=2.0"), - ("2.0+deadbeef", "!=2.0.0"), - ("2.0+deadbeef", "!=2+deadbeef"), - ("2.0+deadbeef", "!=2.0+deadbeef"), - ("2.0+deadbeef", "!=2.0.0+deadbeef"), - ("2.0+deadbeef.0", "!=2.0.0+deadbeef.00"), - // Test the in-equality operation with a prefix - ("2.dev1", "!=2.*"), - ("2a1", "!=2.*"), - ("2a1.post1", "!=2.*"), - ("2b1", "!=2.*"), - ("2b1.dev1", "!=2.*"), - ("2c1", "!=2.*"), - ("2c1.post1.dev1", "!=2.*"), - ("2c1.post1.dev1", "!=2.0.*"), - ("2rc1", "!=2.*"), - ("2rc1", "!=2.0.*"), - ("2", "!=2.*"), - ("2", "!=2.0.*"), - ("2.0", "!=2.*"), - ("2.0.0", "!=2.*"), - // Test the greater than equal operation - ("2.0.dev1", ">=2"), - ("2.0a1", ">=2"), - ("2.0a1.dev1", ">=2"), - ("2.0b1", ">=2"), - ("2.0b1.post1", ">=2"), - ("2.0c1", ">=2"), - ("2.0c1.post1.dev1", ">=2"), - ("2.0rc1", ">=2"), - ("1", ">=2"), - // Test the less than equal operation - ("2.0.post1", "<=2"), - ("2.0.post1.dev1", "<=2"), - ("3", "<=2"), - // Test the greater than operation - ("1", ">2"), - ("2.0.dev1", ">2"), - ("2.0a1", ">2"), - ("2.0a1.post1", ">2"), - ("2.0b1", ">2"), - ("2.0b1.dev1", ">2"), - ("2.0c1", ">2"), - ("2.0c1.post1.dev1", ">2"), - ("2.0rc1", ">2"), - ("2.0", ">2"), - ("2.0.post1", ">2"), - ("2.0.post1.dev1", ">2"), - ("2.0+local.version", ">2"), - // Test the less than operation - ("2.0.dev1", "<2"), - ("2.0a1", "<2"), - ("2.0a1.post1", "<2"), - ("2.0b1", "<2"), - ("2.0b2.dev1", "<2"), - ("2.0c1", "<2"), - ("2.0c1.post1.dev1", "<2"), - ("2.0rc1", "<2"), - ("2.0", "<2"), - ("2.post1", "<2"), - ("2.post1.dev1", "<2"), - ("3", "<2"), - // Test the compatibility operation - ("2.0", "~=1.0"), - ("1.1.0", "~=1.0.0"), - ("1.1.post1", "~=1.0.0"), - // Test that epochs are handled sanely - ("1.0", "~=2!1.0"), - ("2!1.0", "~=1.0"), - ("2!1.0", "==1.0"), - ("1.0", "==2!1.0"), - ("2!1.0", "==1.*"), - ("1.0", "==2!1.*"), - ("2!1.0", "!=2!1.0"), - ]; - for (version, specifier) in pairs { - assert!( - !VersionSpecifier::from_str(specifier) - .unwrap() - .contains(&Version::from_str(version).unwrap()), - "{version} {specifier}" - ); - } - } - - #[test] - fn test_parse_version_specifiers() { - let result = VersionSpecifiers::from_str("~= 0.9, >= 1.0, != 1.3.4.*, < 2.0").unwrap(); - assert_eq!( - result.0, - [ - VersionSpecifier { - operator: Operator::TildeEqual, - version: Version::new([0, 9]), - }, - VersionSpecifier { - operator: Operator::GreaterThanEqual, - version: Version::new([1, 0]), - }, - VersionSpecifier { - operator: Operator::NotEqualStar, - version: Version::new([1, 3, 4]), - }, - VersionSpecifier { - operator: Operator::LessThan, - version: Version::new([2, 0]), - } - ] - ); - } - - #[test] - fn test_parse_error() { - let result = VersionSpecifiers::from_str("~= 0.9, %‍= 1.0, != 1.3.4.*"); - assert_eq!( - result.unwrap_err().to_string(), - indoc! {r" - Failed to parse version: Unexpected end of version specifier, expected operator: - ~= 0.9, %‍= 1.0, != 1.3.4.* - ^^^^^^^ - "} - ); - } - - #[test] - fn test_non_star_after_star() { - let result = VersionSpecifiers::from_str("== 0.9.*.1"); - assert_eq!( - result.unwrap_err().inner.err, - ParseErrorKind::InvalidVersion(version::PatternErrorKind::WildcardNotTrailing.into()) - .into(), - ); - } - - #[test] - fn test_star_wrong_operator() { - let result = VersionSpecifiers::from_str(">= 0.9.1.*"); - assert_eq!( - result.unwrap_err().inner.err, - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorWithStar { - operator: Operator::GreaterThanEqual, - } - .into() - ) - .into(), - ); - } - - #[test] - fn test_invalid_word() { - let result = VersionSpecifiers::from_str("blergh"); - assert_eq!( - result.unwrap_err().inner.err, - ParseErrorKind::MissingOperator.into(), - ); - } - - /// - #[test] - fn test_invalid_specifier() { - let specifiers = [ - // Operator-less specifier - ("2.0", ParseErrorKind::MissingOperator.into()), - // Invalid operator - ( - "=>2.0", - ParseErrorKind::InvalidOperator(OperatorParseError { - got: "=>".to_string(), - }) - .into(), - ), - // Version-less specifier - ("==", ParseErrorKind::MissingVersion.into()), - // Local segment on operators which don't support them - ( - "~=1.0+5", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorLocalCombo { - operator: Operator::TildeEqual, - version: Version::new([1, 0]).with_local(vec![LocalSegment::Number(5)]), - } - .into(), - ) - .into(), - ), - ( - ">=1.0+deadbeef", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorLocalCombo { - operator: Operator::GreaterThanEqual, - version: Version::new([1, 0]) - .with_local(vec![LocalSegment::String("deadbeef".to_string())]), - } - .into(), - ) - .into(), - ), - ( - "<=1.0+abc123", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorLocalCombo { - operator: Operator::LessThanEqual, - version: Version::new([1, 0]) - .with_local(vec![LocalSegment::String("abc123".to_string())]), - } - .into(), - ) - .into(), - ), - ( - ">1.0+watwat", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorLocalCombo { - operator: Operator::GreaterThan, - version: Version::new([1, 0]) - .with_local(vec![LocalSegment::String("watwat".to_string())]), - } - .into(), - ) - .into(), - ), - ( - "<1.0+1.0", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorLocalCombo { - operator: Operator::LessThan, - version: Version::new([1, 0]) - .with_local(vec![LocalSegment::Number(1), LocalSegment::Number(0)]), - } - .into(), - ) - .into(), - ), - // Prefix matching on operators which don't support them - ( - "~=1.0.*", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorWithStar { - operator: Operator::TildeEqual, - } - .into(), - ) - .into(), - ), - ( - ">=1.0.*", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorWithStar { - operator: Operator::GreaterThanEqual, - } - .into(), - ) - .into(), - ), - ( - "<=1.0.*", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorWithStar { - operator: Operator::LessThanEqual, - } - .into(), - ) - .into(), - ), - ( - ">1.0.*", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorWithStar { - operator: Operator::GreaterThan, - } - .into(), - ) - .into(), - ), - ( - "<1.0.*", - ParseErrorKind::InvalidSpecifier( - BuildErrorKind::OperatorWithStar { - operator: Operator::LessThan, - } - .into(), - ) - .into(), - ), - // Combination of local and prefix matching on operators which do - // support one or the other - ( - "==1.0.*+5", - ParseErrorKind::InvalidVersion( - version::PatternErrorKind::WildcardNotTrailing.into(), - ) - .into(), - ), - ( - "!=1.0.*+deadbeef", - ParseErrorKind::InvalidVersion( - version::PatternErrorKind::WildcardNotTrailing.into(), - ) - .into(), - ), - // Prefix matching cannot be used with a pre-release, post-release, - // dev or local version - ( - "==2.0a1.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::UnexpectedEnd { - version: "2.0a1".to_string(), - remaining: ".*".to_string(), - } - .into(), - ) - .into(), - ), - ( - "!=2.0a1.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::UnexpectedEnd { - version: "2.0a1".to_string(), - remaining: ".*".to_string(), - } - .into(), - ) - .into(), - ), - ( - "==2.0.post1.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::UnexpectedEnd { - version: "2.0.post1".to_string(), - remaining: ".*".to_string(), - } - .into(), - ) - .into(), - ), - ( - "!=2.0.post1.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::UnexpectedEnd { - version: "2.0.post1".to_string(), - remaining: ".*".to_string(), - } - .into(), - ) - .into(), - ), - ( - "==2.0.dev1.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::UnexpectedEnd { - version: "2.0.dev1".to_string(), - remaining: ".*".to_string(), - } - .into(), - ) - .into(), - ), - ( - "!=2.0.dev1.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::UnexpectedEnd { - version: "2.0.dev1".to_string(), - remaining: ".*".to_string(), - } - .into(), - ) - .into(), - ), - ( - "==1.0+5.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::LocalEmpty { precursor: '.' }.into(), - ) - .into(), - ), - ( - "!=1.0+deadbeef.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::LocalEmpty { precursor: '.' }.into(), - ) - .into(), - ), - // Prefix matching must appear at the end - ( - "==1.0.*.5", - ParseErrorKind::InvalidVersion( - version::PatternErrorKind::WildcardNotTrailing.into(), - ) - .into(), - ), - // Compatible operator requires 2 digits in the release operator - ( - "~=1", - ParseErrorKind::InvalidSpecifier(BuildErrorKind::CompatibleRelease.into()).into(), - ), - // Cannot use a prefix matching after a .devN version - ( - "==1.0.dev1.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::UnexpectedEnd { - version: "1.0.dev1".to_string(), - remaining: ".*".to_string(), - } - .into(), - ) - .into(), - ), - ( - "!=1.0.dev1.*", - ParseErrorKind::InvalidVersion( - version::ErrorKind::UnexpectedEnd { - version: "1.0.dev1".to_string(), - remaining: ".*".to_string(), - } - .into(), - ) - .into(), - ), - ]; - for (specifier, error) in specifiers { - assert_eq!(VersionSpecifier::from_str(specifier).unwrap_err(), error); - } - } - - #[test] - fn test_display_start() { - assert_eq!( - VersionSpecifier::from_str("== 1.1.*") - .unwrap() - .to_string(), - "==1.1.*" - ); - assert_eq!( - VersionSpecifier::from_str("!= 1.1.*") - .unwrap() - .to_string(), - "!=1.1.*" - ); - } - - #[test] - fn test_version_specifiers_str() { - assert_eq!( - VersionSpecifiers::from_str(">= 3.7").unwrap().to_string(), - ">=3.7" - ); - assert_eq!( - VersionSpecifiers::from_str(">=3.7, < 4.0, != 3.9.0") - .unwrap() - .to_string(), - ">=3.7, !=3.9.0, <4.0" - ); - } - - /// These occur in the simple api, e.g. - /// - #[test] - fn test_version_specifiers_empty() { - assert_eq!(VersionSpecifiers::from_str("").unwrap().to_string(), ""); - } - - /// All non-ASCII version specifiers are invalid, but the user can still - /// attempt to parse a non-ASCII string as a version specifier. This - /// ensures no panics occur and that the error reported has correct info. - #[test] - fn non_ascii_version_specifier() { - let s = "💩"; - let err = s.parse::().unwrap_err(); - assert_eq!(err.inner.start, 0); - assert_eq!(err.inner.end, 4); - - // The first test here is plain ASCII and it gives the - // expected result: the error starts at codepoint 12, - // which is the start of `>5.%`. - let s = ">=3.7, <4.0,>5.%"; - let err = s.parse::().unwrap_err(); - assert_eq!(err.inner.start, 12); - assert_eq!(err.inner.end, 16); - // In this case, we replace a single ASCII codepoint - // with U+3000 IDEOGRAPHIC SPACE. Its *visual* width is - // 2 despite it being a single codepoint. This causes - // the offsets in the error reporting logic to become - // incorrect. - // - // ... it did. This bug was fixed by switching to byte - // offsets. - let s = ">=3.7,\u{3000}<4.0,>5.%"; - let err = s.parse::().unwrap_err(); - assert_eq!(err.inner.start, 14); - assert_eq!(err.inner.end, 18); - } - - /// Tests the human readable error messages generated from an invalid - /// sequence of version specifiers. - #[test] - fn error_message_version_specifiers_parse_error() { - let specs = ">=1.2.3, 5.4.3, >=3.4.5"; - let err = VersionSpecifierParseError { - kind: Box::new(ParseErrorKind::MissingOperator), - }; - let inner = Box::new(VersionSpecifiersParseErrorInner { - err, - line: specs.to_string(), - start: 8, - end: 14, - }); - let err = VersionSpecifiersParseError { inner }; - assert_eq!(err, VersionSpecifiers::from_str(specs).unwrap_err()); - assert_eq!( - err.to_string(), - "\ -Failed to parse version: Unexpected end of version specifier, expected operator: ->=1.2.3, 5.4.3, >=3.4.5 - ^^^^^^ -" - ); - } - - /// Tests the human readable error messages generated when building an - /// invalid version specifier. - #[test] - fn error_message_version_specifier_build_error() { - let err = VersionSpecifierBuildError { - kind: Box::new(BuildErrorKind::CompatibleRelease), - }; - let op = Operator::TildeEqual; - let v = Version::new([5]); - let vpat = VersionPattern::verbatim(v); - assert_eq!(err, VersionSpecifier::from_pattern(op, vpat).unwrap_err()); - assert_eq!( - err.to_string(), - "The ~= operator requires at least two segments in the release version" - ); - } - - /// Tests the human readable error messages generated from parsing invalid - /// version specifier. - #[test] - fn error_message_version_specifier_parse_error() { - let err = VersionSpecifierParseError { - kind: Box::new(ParseErrorKind::InvalidSpecifier( - VersionSpecifierBuildError { - kind: Box::new(BuildErrorKind::CompatibleRelease), - }, - )), - }; - assert_eq!(err, VersionSpecifier::from_str("~=5").unwrap_err()); - assert_eq!( - err.to_string(), - "The ~= operator requires at least two segments in the release version" - ); - } -} +mod tests; diff --git a/crates/uv-pep440/src/version_specifier/tests.rs b/crates/uv-pep440/src/version_specifier/tests.rs new file mode 100644 index 000000000..f853aab28 --- /dev/null +++ b/crates/uv-pep440/src/version_specifier/tests.rs @@ -0,0 +1,948 @@ +use std::{cmp::Ordering, str::FromStr}; + +use indoc::indoc; + +use crate::LocalSegment; + +use super::*; + +/// +#[test] +fn test_equal() { + let version = Version::from_str("1.1.post1").unwrap(); + + assert!(!VersionSpecifier::from_str("== 1.1") + .unwrap() + .contains(&version)); + assert!(VersionSpecifier::from_str("== 1.1.post1") + .unwrap() + .contains(&version)); + assert!(VersionSpecifier::from_str("== 1.1.*") + .unwrap() + .contains(&version)); +} + +const VERSIONS_ALL: &[&str] = &[ + // Implicit epoch of 0 + "1.0.dev456", + "1.0a1", + "1.0a2.dev456", + "1.0a12.dev456", + "1.0a12", + "1.0b1.dev456", + "1.0b2", + "1.0b2.post345.dev456", + "1.0b2.post345", + "1.0b2-346", + "1.0c1.dev456", + "1.0c1", + "1.0rc2", + "1.0c3", + "1.0", + "1.0.post456.dev34", + "1.0.post456", + "1.1.dev1", + "1.2+123abc", + "1.2+123abc456", + "1.2+abc", + "1.2+abc123", + "1.2+abc123def", + "1.2+1234.abc", + "1.2+123456", + "1.2.r32+123456", + "1.2.rev33+123456", + // Explicit epoch of 1 + "1!1.0.dev456", + "1!1.0a1", + "1!1.0a2.dev456", + "1!1.0a12.dev456", + "1!1.0a12", + "1!1.0b1.dev456", + "1!1.0b2", + "1!1.0b2.post345.dev456", + "1!1.0b2.post345", + "1!1.0b2-346", + "1!1.0c1.dev456", + "1!1.0c1", + "1!1.0rc2", + "1!1.0c3", + "1!1.0", + "1!1.0.post456.dev34", + "1!1.0.post456", + "1!1.1.dev1", + "1!1.2+123abc", + "1!1.2+123abc456", + "1!1.2+abc", + "1!1.2+abc123", + "1!1.2+abc123def", + "1!1.2+1234.abc", + "1!1.2+123456", + "1!1.2.r32+123456", + "1!1.2.rev33+123456", +]; + +/// +/// +/// +/// These tests are a lot shorter than the pypa/packaging version since we implement all +/// comparisons through one method +#[test] +fn test_operators_true() { + let versions: Vec = VERSIONS_ALL + .iter() + .map(|version| Version::from_str(version).unwrap()) + .collect(); + + // Below we'll generate every possible combination of VERSIONS_ALL that + // should be true for the given operator + let operations = [ + // Verify that the less than (<) operator works correctly + versions + .iter() + .enumerate() + .flat_map(|(i, x)| { + versions[i + 1..] + .iter() + .map(move |y| (x, y, Ordering::Less)) + }) + .collect::>(), + // Verify that the equal (==) operator works correctly + versions + .iter() + .map(move |x| (x, x, Ordering::Equal)) + .collect::>(), + // Verify that the greater than (>) operator works correctly + versions + .iter() + .enumerate() + .flat_map(|(i, x)| versions[..i].iter().map(move |y| (x, y, Ordering::Greater))) + .collect::>(), + ] + .into_iter() + .flatten(); + + for (a, b, ordering) in operations { + assert_eq!(a.cmp(b), ordering, "{a} {ordering:?} {b}"); + } +} + +const VERSIONS_0: &[&str] = &[ + "1.0.dev456", + "1.0a1", + "1.0a2.dev456", + "1.0a12.dev456", + "1.0a12", + "1.0b1.dev456", + "1.0b2", + "1.0b2.post345.dev456", + "1.0b2.post345", + "1.0b2-346", + "1.0c1.dev456", + "1.0c1", + "1.0rc2", + "1.0c3", + "1.0", + "1.0.post456.dev34", + "1.0.post456", + "1.1.dev1", + "1.2+123abc", + "1.2+123abc456", + "1.2+abc", + "1.2+abc123", + "1.2+abc123def", + "1.2+1234.abc", + "1.2+123456", + "1.2.r32+123456", + "1.2.rev33+123456", +]; + +const SPECIFIERS_OTHER: &[&str] = &[ + "== 1.*", "== 1.0.*", "== 1.1.*", "== 1.2.*", "== 2.*", "~= 1.0", "~= 1.0b1", "~= 1.1", + "~= 1.2", "~= 2.0", +]; + +const EXPECTED_OTHER: &[[bool; 10]] = &[ + [ + true, true, false, false, false, false, false, false, false, false, + ], + [ + true, true, false, false, false, false, false, false, false, false, + ], + [ + true, true, false, false, false, false, false, false, false, false, + ], + [ + true, true, false, false, false, false, false, false, false, false, + ], + [ + true, true, false, false, false, false, false, false, false, false, + ], + [ + true, true, false, false, false, false, false, false, false, false, + ], + [ + true, true, false, false, false, false, true, false, false, false, + ], + [ + true, true, false, false, false, false, true, false, false, false, + ], + [ + true, true, false, false, false, false, true, false, false, false, + ], + [ + true, true, false, false, false, false, true, false, false, false, + ], + [ + true, true, false, false, false, false, true, false, false, false, + ], + [ + true, true, false, false, false, false, true, false, false, false, + ], + [ + true, true, false, false, false, false, true, false, false, false, + ], + [ + true, true, false, false, false, false, true, false, false, false, + ], + [ + true, true, false, false, false, true, true, false, false, false, + ], + [ + true, true, false, false, false, true, true, false, false, false, + ], + [ + true, true, false, false, false, true, true, false, false, false, + ], + [ + true, false, true, false, false, true, true, false, false, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], + [ + true, false, false, true, false, true, true, true, true, false, + ], +]; + +/// Test for tilde equal (~=) and star equal (== x.y.*) recorded from pypa/packaging +/// +/// Well, except for +#[test] +fn test_operators_other() { + let versions = VERSIONS_0 + .iter() + .map(|version| Version::from_str(version).unwrap()); + let specifiers: Vec<_> = SPECIFIERS_OTHER + .iter() + .map(|specifier| VersionSpecifier::from_str(specifier).unwrap()) + .collect(); + + for (version, expected) in versions.zip(EXPECTED_OTHER) { + let actual = specifiers + .iter() + .map(|specifier| specifier.contains(&version)); + for ((actual, expected), _specifier) in actual.zip(expected).zip(SPECIFIERS_OTHER) { + assert_eq!(actual, *expected); + } + } +} + +#[test] +fn test_arbitrary_equality() { + assert!(VersionSpecifier::from_str("=== 1.2a1") + .unwrap() + .contains(&Version::from_str("1.2a1").unwrap())); + assert!(!VersionSpecifier::from_str("=== 1.2a1") + .unwrap() + .contains(&Version::from_str("1.2a1+local").unwrap())); +} + +#[test] +fn test_specifiers_true() { + let pairs = [ + // Test the equality operation + ("2.0", "==2"), + ("2.0", "==2.0"), + ("2.0", "==2.0.0"), + ("2.0+deadbeef", "==2"), + ("2.0+deadbeef", "==2.0"), + ("2.0+deadbeef", "==2.0.0"), + ("2.0+deadbeef", "==2+deadbeef"), + ("2.0+deadbeef", "==2.0+deadbeef"), + ("2.0+deadbeef", "==2.0.0+deadbeef"), + ("2.0+deadbeef.0", "==2.0.0+deadbeef.00"), + // Test the equality operation with a prefix + ("2.dev1", "==2.*"), + ("2a1", "==2.*"), + ("2a1.post1", "==2.*"), + ("2b1", "==2.*"), + ("2b1.dev1", "==2.*"), + ("2c1", "==2.*"), + ("2c1.post1.dev1", "==2.*"), + ("2c1.post1.dev1", "==2.0.*"), + ("2rc1", "==2.*"), + ("2rc1", "==2.0.*"), + ("2", "==2.*"), + ("2", "==2.0.*"), + ("2", "==0!2.*"), + ("0!2", "==2.*"), + ("2.0", "==2.*"), + ("2.0.0", "==2.*"), + ("2.1+local.version", "==2.1.*"), + // Test the in-equality operation + ("2.1", "!=2"), + ("2.1", "!=2.0"), + ("2.0.1", "!=2"), + ("2.0.1", "!=2.0"), + ("2.0.1", "!=2.0.0"), + ("2.0", "!=2.0+deadbeef"), + // Test the in-equality operation with a prefix + ("2.0", "!=3.*"), + ("2.1", "!=2.0.*"), + // Test the greater than equal operation + ("2.0", ">=2"), + ("2.0", ">=2.0"), + ("2.0", ">=2.0.0"), + ("2.0.post1", ">=2"), + ("2.0.post1.dev1", ">=2"), + ("3", ">=2"), + // Test the less than equal operation + ("2.0", "<=2"), + ("2.0", "<=2.0"), + ("2.0", "<=2.0.0"), + ("2.0.dev1", "<=2"), + ("2.0a1", "<=2"), + ("2.0a1.dev1", "<=2"), + ("2.0b1", "<=2"), + ("2.0b1.post1", "<=2"), + ("2.0c1", "<=2"), + ("2.0c1.post1.dev1", "<=2"), + ("2.0rc1", "<=2"), + ("1", "<=2"), + // Test the greater than operation + ("3", ">2"), + ("2.1", ">2.0"), + ("2.0.1", ">2"), + ("2.1.post1", ">2"), + ("2.1+local.version", ">2"), + // Test the less than operation + ("1", "<2"), + ("2.0", "<2.1"), + ("2.0.dev0", "<2.1"), + // Test the compatibility operation + ("1", "~=1.0"), + ("1.0.1", "~=1.0"), + ("1.1", "~=1.0"), + ("1.9999999", "~=1.0"), + ("1.1", "~=1.0a1"), + ("2022.01.01", "~=2022.01.01"), + // Test that epochs are handled sanely + ("2!1.0", "~=2!1.0"), + ("2!1.0", "==2!1.*"), + ("2!1.0", "==2!1.0"), + ("2!1.0", "!=1.0"), + ("1.0", "!=2!1.0"), + ("1.0", "<=2!0.1"), + ("2!1.0", ">=2.0"), + ("1.0", "<2!0.1"), + ("2!1.0", ">2.0"), + // Test some normalization rules + ("2.0.5", ">2.0dev"), + ]; + + for (s_version, s_spec) in pairs { + let version = s_version.parse::().unwrap(); + let spec = s_spec.parse::().unwrap(); + assert!( + spec.contains(&version), + "{s_version} {s_spec}\nversion repr: {:?}\nspec version repr: {:?}", + version.as_bloated_debug(), + spec.version.as_bloated_debug(), + ); + } +} + +#[test] +fn test_specifier_false() { + let pairs = [ + // Test the equality operation + ("2.1", "==2"), + ("2.1", "==2.0"), + ("2.1", "==2.0.0"), + ("2.0", "==2.0+deadbeef"), + // Test the equality operation with a prefix + ("2.0", "==3.*"), + ("2.1", "==2.0.*"), + // Test the in-equality operation + ("2.0", "!=2"), + ("2.0", "!=2.0"), + ("2.0", "!=2.0.0"), + ("2.0+deadbeef", "!=2"), + ("2.0+deadbeef", "!=2.0"), + ("2.0+deadbeef", "!=2.0.0"), + ("2.0+deadbeef", "!=2+deadbeef"), + ("2.0+deadbeef", "!=2.0+deadbeef"), + ("2.0+deadbeef", "!=2.0.0+deadbeef"), + ("2.0+deadbeef.0", "!=2.0.0+deadbeef.00"), + // Test the in-equality operation with a prefix + ("2.dev1", "!=2.*"), + ("2a1", "!=2.*"), + ("2a1.post1", "!=2.*"), + ("2b1", "!=2.*"), + ("2b1.dev1", "!=2.*"), + ("2c1", "!=2.*"), + ("2c1.post1.dev1", "!=2.*"), + ("2c1.post1.dev1", "!=2.0.*"), + ("2rc1", "!=2.*"), + ("2rc1", "!=2.0.*"), + ("2", "!=2.*"), + ("2", "!=2.0.*"), + ("2.0", "!=2.*"), + ("2.0.0", "!=2.*"), + // Test the greater than equal operation + ("2.0.dev1", ">=2"), + ("2.0a1", ">=2"), + ("2.0a1.dev1", ">=2"), + ("2.0b1", ">=2"), + ("2.0b1.post1", ">=2"), + ("2.0c1", ">=2"), + ("2.0c1.post1.dev1", ">=2"), + ("2.0rc1", ">=2"), + ("1", ">=2"), + // Test the less than equal operation + ("2.0.post1", "<=2"), + ("2.0.post1.dev1", "<=2"), + ("3", "<=2"), + // Test the greater than operation + ("1", ">2"), + ("2.0.dev1", ">2"), + ("2.0a1", ">2"), + ("2.0a1.post1", ">2"), + ("2.0b1", ">2"), + ("2.0b1.dev1", ">2"), + ("2.0c1", ">2"), + ("2.0c1.post1.dev1", ">2"), + ("2.0rc1", ">2"), + ("2.0", ">2"), + ("2.0.post1", ">2"), + ("2.0.post1.dev1", ">2"), + ("2.0+local.version", ">2"), + // Test the less than operation + ("2.0.dev1", "<2"), + ("2.0a1", "<2"), + ("2.0a1.post1", "<2"), + ("2.0b1", "<2"), + ("2.0b2.dev1", "<2"), + ("2.0c1", "<2"), + ("2.0c1.post1.dev1", "<2"), + ("2.0rc1", "<2"), + ("2.0", "<2"), + ("2.post1", "<2"), + ("2.post1.dev1", "<2"), + ("3", "<2"), + // Test the compatibility operation + ("2.0", "~=1.0"), + ("1.1.0", "~=1.0.0"), + ("1.1.post1", "~=1.0.0"), + // Test that epochs are handled sanely + ("1.0", "~=2!1.0"), + ("2!1.0", "~=1.0"), + ("2!1.0", "==1.0"), + ("1.0", "==2!1.0"), + ("2!1.0", "==1.*"), + ("1.0", "==2!1.*"), + ("2!1.0", "!=2!1.0"), + ]; + for (version, specifier) in pairs { + assert!( + !VersionSpecifier::from_str(specifier) + .unwrap() + .contains(&Version::from_str(version).unwrap()), + "{version} {specifier}" + ); + } +} + +#[test] +fn test_parse_version_specifiers() { + let result = VersionSpecifiers::from_str("~= 0.9, >= 1.0, != 1.3.4.*, < 2.0").unwrap(); + assert_eq!( + result.0, + [ + VersionSpecifier { + operator: Operator::TildeEqual, + version: Version::new([0, 9]), + }, + VersionSpecifier { + operator: Operator::GreaterThanEqual, + version: Version::new([1, 0]), + }, + VersionSpecifier { + operator: Operator::NotEqualStar, + version: Version::new([1, 3, 4]), + }, + VersionSpecifier { + operator: Operator::LessThan, + version: Version::new([2, 0]), + } + ] + ); +} + +#[test] +fn test_parse_error() { + let result = VersionSpecifiers::from_str("~= 0.9, %‍= 1.0, != 1.3.4.*"); + assert_eq!( + result.unwrap_err().to_string(), + indoc! {r" + Failed to parse version: Unexpected end of version specifier, expected operator: + ~= 0.9, %‍= 1.0, != 1.3.4.* + ^^^^^^^ + "} + ); +} + +#[test] +fn test_non_star_after_star() { + let result = VersionSpecifiers::from_str("== 0.9.*.1"); + assert_eq!( + result.unwrap_err().inner.err, + ParseErrorKind::InvalidVersion(version::PatternErrorKind::WildcardNotTrailing.into()) + .into(), + ); +} + +#[test] +fn test_star_wrong_operator() { + let result = VersionSpecifiers::from_str(">= 0.9.1.*"); + assert_eq!( + result.unwrap_err().inner.err, + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorWithStar { + operator: Operator::GreaterThanEqual, + } + .into() + ) + .into(), + ); +} + +#[test] +fn test_invalid_word() { + let result = VersionSpecifiers::from_str("blergh"); + assert_eq!( + result.unwrap_err().inner.err, + ParseErrorKind::MissingOperator.into(), + ); +} + +/// +#[test] +fn test_invalid_specifier() { + let specifiers = [ + // Operator-less specifier + ("2.0", ParseErrorKind::MissingOperator.into()), + // Invalid operator + ( + "=>2.0", + ParseErrorKind::InvalidOperator(OperatorParseError { + got: "=>".to_string(), + }) + .into(), + ), + // Version-less specifier + ("==", ParseErrorKind::MissingVersion.into()), + // Local segment on operators which don't support them + ( + "~=1.0+5", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorLocalCombo { + operator: Operator::TildeEqual, + version: Version::new([1, 0]).with_local(vec![LocalSegment::Number(5)]), + } + .into(), + ) + .into(), + ), + ( + ">=1.0+deadbeef", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorLocalCombo { + operator: Operator::GreaterThanEqual, + version: Version::new([1, 0]) + .with_local(vec![LocalSegment::String("deadbeef".to_string())]), + } + .into(), + ) + .into(), + ), + ( + "<=1.0+abc123", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorLocalCombo { + operator: Operator::LessThanEqual, + version: Version::new([1, 0]) + .with_local(vec![LocalSegment::String("abc123".to_string())]), + } + .into(), + ) + .into(), + ), + ( + ">1.0+watwat", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorLocalCombo { + operator: Operator::GreaterThan, + version: Version::new([1, 0]) + .with_local(vec![LocalSegment::String("watwat".to_string())]), + } + .into(), + ) + .into(), + ), + ( + "<1.0+1.0", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorLocalCombo { + operator: Operator::LessThan, + version: Version::new([1, 0]) + .with_local(vec![LocalSegment::Number(1), LocalSegment::Number(0)]), + } + .into(), + ) + .into(), + ), + // Prefix matching on operators which don't support them + ( + "~=1.0.*", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorWithStar { + operator: Operator::TildeEqual, + } + .into(), + ) + .into(), + ), + ( + ">=1.0.*", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorWithStar { + operator: Operator::GreaterThanEqual, + } + .into(), + ) + .into(), + ), + ( + "<=1.0.*", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorWithStar { + operator: Operator::LessThanEqual, + } + .into(), + ) + .into(), + ), + ( + ">1.0.*", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorWithStar { + operator: Operator::GreaterThan, + } + .into(), + ) + .into(), + ), + ( + "<1.0.*", + ParseErrorKind::InvalidSpecifier( + BuildErrorKind::OperatorWithStar { + operator: Operator::LessThan, + } + .into(), + ) + .into(), + ), + // Combination of local and prefix matching on operators which do + // support one or the other + ( + "==1.0.*+5", + ParseErrorKind::InvalidVersion(version::PatternErrorKind::WildcardNotTrailing.into()) + .into(), + ), + ( + "!=1.0.*+deadbeef", + ParseErrorKind::InvalidVersion(version::PatternErrorKind::WildcardNotTrailing.into()) + .into(), + ), + // Prefix matching cannot be used with a pre-release, post-release, + // dev or local version + ( + "==2.0a1.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::UnexpectedEnd { + version: "2.0a1".to_string(), + remaining: ".*".to_string(), + } + .into(), + ) + .into(), + ), + ( + "!=2.0a1.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::UnexpectedEnd { + version: "2.0a1".to_string(), + remaining: ".*".to_string(), + } + .into(), + ) + .into(), + ), + ( + "==2.0.post1.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::UnexpectedEnd { + version: "2.0.post1".to_string(), + remaining: ".*".to_string(), + } + .into(), + ) + .into(), + ), + ( + "!=2.0.post1.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::UnexpectedEnd { + version: "2.0.post1".to_string(), + remaining: ".*".to_string(), + } + .into(), + ) + .into(), + ), + ( + "==2.0.dev1.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::UnexpectedEnd { + version: "2.0.dev1".to_string(), + remaining: ".*".to_string(), + } + .into(), + ) + .into(), + ), + ( + "!=2.0.dev1.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::UnexpectedEnd { + version: "2.0.dev1".to_string(), + remaining: ".*".to_string(), + } + .into(), + ) + .into(), + ), + ( + "==1.0+5.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::LocalEmpty { precursor: '.' }.into(), + ) + .into(), + ), + ( + "!=1.0+deadbeef.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::LocalEmpty { precursor: '.' }.into(), + ) + .into(), + ), + // Prefix matching must appear at the end + ( + "==1.0.*.5", + ParseErrorKind::InvalidVersion(version::PatternErrorKind::WildcardNotTrailing.into()) + .into(), + ), + // Compatible operator requires 2 digits in the release operator + ( + "~=1", + ParseErrorKind::InvalidSpecifier(BuildErrorKind::CompatibleRelease.into()).into(), + ), + // Cannot use a prefix matching after a .devN version + ( + "==1.0.dev1.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::UnexpectedEnd { + version: "1.0.dev1".to_string(), + remaining: ".*".to_string(), + } + .into(), + ) + .into(), + ), + ( + "!=1.0.dev1.*", + ParseErrorKind::InvalidVersion( + version::ErrorKind::UnexpectedEnd { + version: "1.0.dev1".to_string(), + remaining: ".*".to_string(), + } + .into(), + ) + .into(), + ), + ]; + for (specifier, error) in specifiers { + assert_eq!(VersionSpecifier::from_str(specifier).unwrap_err(), error); + } +} + +#[test] +fn test_display_start() { + assert_eq!( + VersionSpecifier::from_str("== 1.1.*") + .unwrap() + .to_string(), + "==1.1.*" + ); + assert_eq!( + VersionSpecifier::from_str("!= 1.1.*") + .unwrap() + .to_string(), + "!=1.1.*" + ); +} + +#[test] +fn test_version_specifiers_str() { + assert_eq!( + VersionSpecifiers::from_str(">= 3.7").unwrap().to_string(), + ">=3.7" + ); + assert_eq!( + VersionSpecifiers::from_str(">=3.7, < 4.0, != 3.9.0") + .unwrap() + .to_string(), + ">=3.7, !=3.9.0, <4.0" + ); +} + +/// These occur in the simple api, e.g. +/// +#[test] +fn test_version_specifiers_empty() { + assert_eq!(VersionSpecifiers::from_str("").unwrap().to_string(), ""); +} + +/// All non-ASCII version specifiers are invalid, but the user can still +/// attempt to parse a non-ASCII string as a version specifier. This +/// ensures no panics occur and that the error reported has correct info. +#[test] +fn non_ascii_version_specifier() { + let s = "💩"; + let err = s.parse::().unwrap_err(); + assert_eq!(err.inner.start, 0); + assert_eq!(err.inner.end, 4); + + // The first test here is plain ASCII and it gives the + // expected result: the error starts at codepoint 12, + // which is the start of `>5.%`. + let s = ">=3.7, <4.0,>5.%"; + let err = s.parse::().unwrap_err(); + assert_eq!(err.inner.start, 12); + assert_eq!(err.inner.end, 16); + // In this case, we replace a single ASCII codepoint + // with U+3000 IDEOGRAPHIC SPACE. Its *visual* width is + // 2 despite it being a single codepoint. This causes + // the offsets in the error reporting logic to become + // incorrect. + // + // ... it did. This bug was fixed by switching to byte + // offsets. + let s = ">=3.7,\u{3000}<4.0,>5.%"; + let err = s.parse::().unwrap_err(); + assert_eq!(err.inner.start, 14); + assert_eq!(err.inner.end, 18); +} + +/// Tests the human readable error messages generated from an invalid +/// sequence of version specifiers. +#[test] +fn error_message_version_specifiers_parse_error() { + let specs = ">=1.2.3, 5.4.3, >=3.4.5"; + let err = VersionSpecifierParseError { + kind: Box::new(ParseErrorKind::MissingOperator), + }; + let inner = Box::new(VersionSpecifiersParseErrorInner { + err, + line: specs.to_string(), + start: 8, + end: 14, + }); + let err = VersionSpecifiersParseError { inner }; + assert_eq!(err, VersionSpecifiers::from_str(specs).unwrap_err()); + assert_eq!( + err.to_string(), + "\ +Failed to parse version: Unexpected end of version specifier, expected operator: +>=1.2.3, 5.4.3, >=3.4.5 + ^^^^^^ +" + ); +} + +/// Tests the human readable error messages generated when building an +/// invalid version specifier. +#[test] +fn error_message_version_specifier_build_error() { + let err = VersionSpecifierBuildError { + kind: Box::new(BuildErrorKind::CompatibleRelease), + }; + let op = Operator::TildeEqual; + let v = Version::new([5]); + let vpat = VersionPattern::verbatim(v); + assert_eq!(err, VersionSpecifier::from_pattern(op, vpat).unwrap_err()); + assert_eq!( + err.to_string(), + "The ~= operator requires at least two segments in the release version" + ); +} + +/// Tests the human readable error messages generated from parsing invalid +/// version specifier. +#[test] +fn error_message_version_specifier_parse_error() { + let err = VersionSpecifierParseError { + kind: Box::new(ParseErrorKind::InvalidSpecifier( + VersionSpecifierBuildError { + kind: Box::new(BuildErrorKind::CompatibleRelease), + }, + )), + }; + assert_eq!(err, VersionSpecifier::from_str("~=5").unwrap_err()); + assert_eq!( + err.to_string(), + "The ~= operator requires at least two segments in the release version" + ); +} diff --git a/crates/uv-pep508/Cargo.toml b/crates/uv-pep508/Cargo.toml index e2f93592c..a5d5dbc71 100644 --- a/crates/uv-pep508/Cargo.toml +++ b/crates/uv-pep508/Cargo.toml @@ -15,6 +15,7 @@ authors = { workspace = true } [lib] name = "uv_pep508" crate-type = ["cdylib", "rlib"] +doctest = false [lints] workspace = true diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index af3c335be..8668a9e52 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -933,7 +933,7 @@ fn parse_pep508_requirement( // // See: https://github.com/pypa/pip/blob/111eed14b6e9fba7c78a5ec2b7594812d17b5d2b/src/pip/_internal/utils/filetypes.py#L8 if requirement_kind.is_none() { - if looks_like_archive(cursor.slice(name_start, name_end)) { + if looks_like_archive(cursor.slice(name_start, name_end - name_start)) { let clone = cursor.clone().at(start); return Err(Pep508Error { message: Pep508ErrorSource::UnsupportedRequirement("URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ https://...`).".to_string()), @@ -997,787 +997,5 @@ fn parse_pep508_requirement( }) } -/// Half of these tests are copied from #[cfg(test)] -mod tests { - use std::env; - use std::str::FromStr; - - use insta::assert_snapshot; - use url::Url; - - use uv_normalize::{ExtraName, InvalidNameError, PackageName}; - use uv_pep440::{Operator, Version, VersionPattern, VersionSpecifier}; - - use crate::cursor::Cursor; - use crate::marker::{parse, MarkerExpression, MarkerTree, MarkerValueVersion}; - use crate::{ - MarkerOperator, MarkerValueString, Requirement, TracingReporter, VerbatimUrl, VersionOrUrl, - }; - - fn parse_pep508_err(input: &str) -> String { - Requirement::::from_str(input) - .unwrap_err() - .to_string() - } - - #[cfg(feature = "non-pep508-extensions")] - fn parse_unnamed_err(input: &str) -> String { - crate::UnnamedRequirement::::from_str(input) - .unwrap_err() - .to_string() - } - - #[cfg(windows)] - #[test] - fn test_preprocess_url_windows() { - use std::path::PathBuf; - - let actual = crate::parse_url::( - &mut Cursor::new("file:///C:/Users/ferris/wheel-0.42.0.tar.gz"), - None, - ) - .unwrap() - .to_file_path(); - let expected = PathBuf::from(r"C:\Users\ferris\wheel-0.42.0.tar.gz"); - assert_eq!(actual, Ok(expected)); - } - - #[test] - fn error_empty() { - assert_snapshot!( - parse_pep508_err(""), - @r" - Empty field is not allowed for PEP508 - - ^" - ); - } - - #[test] - fn error_start() { - assert_snapshot!( - parse_pep508_err("_name"), - @" - Expected package name starting with an alphanumeric character, found `_` - _name - ^" - ); - } - - #[test] - fn error_end() { - assert_snapshot!( - parse_pep508_err("name_"), - @" - Package name must end with an alphanumeric character, not '_' - name_ - ^" - ); - } - - #[test] - fn basic_examples() { - let input = r"requests[security,tests]==2.8.*,>=2.8.1 ; python_full_version < '2.7'"; - let requests = Requirement::::from_str(input).unwrap(); - assert_eq!(input, requests.to_string()); - let expected = Requirement { - name: PackageName::from_str("requests").unwrap(), - extras: vec![ - ExtraName::from_str("security").unwrap(), - ExtraName::from_str("tests").unwrap(), - ], - version_or_url: Some(VersionOrUrl::VersionSpecifier( - [ - VersionSpecifier::from_pattern( - Operator::Equal, - VersionPattern::wildcard(Version::new([2, 8])), - ) - .unwrap(), - VersionSpecifier::from_pattern( - Operator::GreaterThanEqual, - VersionPattern::verbatim(Version::new([2, 8, 1])), - ) - .unwrap(), - ] - .into_iter() - .collect(), - )), - marker: MarkerTree::expression(MarkerExpression::Version { - key: MarkerValueVersion::PythonFullVersion, - specifier: VersionSpecifier::from_pattern( - uv_pep440::Operator::LessThan, - "2.7".parse().unwrap(), - ) - .unwrap(), - }), - origin: None, - }; - assert_eq!(requests, expected); - } - - #[test] - fn parenthesized_single() { - let numpy = Requirement::::from_str("numpy ( >=1.19 )").unwrap(); - assert_eq!(numpy.name.as_ref(), "numpy"); - } - - #[test] - fn parenthesized_double() { - let numpy = Requirement::::from_str("numpy ( >=1.19, <2.0 )").unwrap(); - assert_eq!(numpy.name.as_ref(), "numpy"); - } - - #[test] - fn versions_single() { - let numpy = Requirement::::from_str("numpy >=1.19 ").unwrap(); - assert_eq!(numpy.name.as_ref(), "numpy"); - } - - #[test] - fn versions_double() { - let numpy = Requirement::::from_str("numpy >=1.19, <2.0 ").unwrap(); - assert_eq!(numpy.name.as_ref(), "numpy"); - } - - #[test] - #[cfg(feature = "non-pep508-extensions")] - fn direct_url_no_extras() { - let numpy = crate::UnnamedRequirement::::from_str("https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl").unwrap(); - assert_eq!(numpy.url.to_string(), "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl"); - assert_eq!(numpy.extras, vec![]); - } - - #[test] - #[cfg(all(unix, feature = "non-pep508-extensions"))] - fn direct_url_extras() { - let numpy = crate::UnnamedRequirement::::from_str( - "/path/to/numpy-1.26.4-cp312-cp312-win32.whl[dev]", - ) - .unwrap(); - assert_eq!( - numpy.url.to_string(), - "file:///path/to/numpy-1.26.4-cp312-cp312-win32.whl" - ); - assert_eq!(numpy.extras, vec![ExtraName::from_str("dev").unwrap()]); - } - - #[test] - #[cfg(all(windows, feature = "non-pep508-extensions"))] - fn direct_url_extras() { - let numpy = crate::UnnamedRequirement::::from_str( - "C:\\path\\to\\numpy-1.26.4-cp312-cp312-win32.whl[dev]", - ) - .unwrap(); - assert_eq!( - numpy.url.to_string(), - "file:///C:/path/to/numpy-1.26.4-cp312-cp312-win32.whl" - ); - assert_eq!(numpy.extras, vec![ExtraName::from_str("dev").unwrap()]); - } - - #[test] - fn error_extras_eof1() { - assert_snapshot!( - parse_pep508_err("black["), - @" - Missing closing bracket (expected ']', found end of dependency specification) - black[ - ^" - ); - } - - #[test] - fn error_extras_eof2() { - assert_snapshot!( - parse_pep508_err("black[d"), - @" - Missing closing bracket (expected ']', found end of dependency specification) - black[d - ^" - ); - } - - #[test] - fn error_extras_eof3() { - assert_snapshot!( - parse_pep508_err("black[d,"), - @" - Missing closing bracket (expected ']', found end of dependency specification) - black[d, - ^" - ); - } - - #[test] - fn error_extras_illegal_start1() { - assert_snapshot!( - parse_pep508_err("black[ö]"), - @" - Expected an alphanumeric character starting the extra name, found `ö` - black[ö] - ^" - ); - } - - #[test] - fn error_extras_illegal_start2() { - assert_snapshot!( - parse_pep508_err("black[_d]"), - @" - Expected an alphanumeric character starting the extra name, found `_` - black[_d] - ^" - ); - } - - #[test] - fn error_extras_illegal_start3() { - assert_snapshot!( - parse_pep508_err("black[,]"), - @" - Expected either alphanumerical character (starting the extra name) or `]` (ending the extras section), found `,` - black[,] - ^" - ); - } - - #[test] - fn error_extras_illegal_character() { - assert_snapshot!( - parse_pep508_err("black[jüpyter]"), - @" - Invalid character in extras name, expected an alphanumeric character, `-`, `_`, `.`, `,` or `]`, found `ü` - black[jüpyter] - ^" - ); - } - - #[test] - fn error_extras1() { - let numpy = Requirement::::from_str("black[d]").unwrap(); - assert_eq!(numpy.extras, vec![ExtraName::from_str("d").unwrap()]); - } - - #[test] - fn error_extras2() { - let numpy = Requirement::::from_str("black[d,jupyter]").unwrap(); - assert_eq!( - numpy.extras, - vec![ - ExtraName::from_str("d").unwrap(), - ExtraName::from_str("jupyter").unwrap(), - ] - ); - } - - #[test] - fn empty_extras() { - let black = Requirement::::from_str("black[]").unwrap(); - assert_eq!(black.extras, vec![]); - } - - #[test] - fn empty_extras_with_spaces() { - let black = Requirement::::from_str("black[ ]").unwrap(); - assert_eq!(black.extras, vec![]); - } - - #[test] - fn error_extra_with_trailing_comma() { - assert_snapshot!( - parse_pep508_err("black[d,]"), - @" - Expected an alphanumeric character starting the extra name, found `]` - black[d,] - ^" - ); - } - - #[test] - fn error_parenthesized_pep440() { - assert_snapshot!( - parse_pep508_err("numpy ( ><1.19 )"), - @" - no such comparison operator \"><\", must be one of ~= == != <= >= < > === - numpy ( ><1.19 ) - ^^^^^^^" - ); - } - - #[test] - fn error_parenthesized_parenthesis() { - assert_snapshot!( - parse_pep508_err("numpy ( >=1.19"), - @" - Missing closing parenthesis (expected ')', found end of dependency specification) - numpy ( >=1.19 - ^" - ); - } - - #[test] - fn error_whats_that() { - assert_snapshot!( - parse_pep508_err("numpy % 1.16"), - @" - Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `%` - numpy % 1.16 - ^" - ); - } - - #[test] - fn url() { - let pip_url = - Requirement::from_str("pip @ https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686") - .unwrap(); - let url = "https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686"; - let expected = Requirement { - name: PackageName::from_str("pip").unwrap(), - extras: vec![], - marker: MarkerTree::TRUE, - version_or_url: Some(VersionOrUrl::Url(Url::parse(url).unwrap())), - origin: None, - }; - assert_eq!(pip_url, expected); - } - - #[test] - fn test_marker_parsing() { - let marker = r#"python_version == "2.7" and (sys_platform == "win32" or (os_name == "linux" and implementation_name == 'cpython'))"#; - let actual = parse::parse_markers_cursor::( - &mut Cursor::new(marker), - &mut TracingReporter, - ) - .unwrap() - .unwrap(); - - let mut a = MarkerTree::expression(MarkerExpression::Version { - key: MarkerValueVersion::PythonVersion, - specifier: VersionSpecifier::from_pattern( - uv_pep440::Operator::Equal, - "2.7".parse().unwrap(), - ) - .unwrap(), - }); - let mut b = MarkerTree::expression(MarkerExpression::String { - key: MarkerValueString::SysPlatform, - operator: MarkerOperator::Equal, - value: "win32".to_string(), - }); - let mut c = MarkerTree::expression(MarkerExpression::String { - key: MarkerValueString::OsName, - operator: MarkerOperator::Equal, - value: "linux".to_string(), - }); - let d = MarkerTree::expression(MarkerExpression::String { - key: MarkerValueString::ImplementationName, - operator: MarkerOperator::Equal, - value: "cpython".to_string(), - }); - - c.and(d); - b.or(c); - a.and(b); - - assert_eq!(a, actual); - } - - #[test] - fn name_and_marker() { - Requirement::::from_str(r#"numpy; sys_platform == "win32" or (os_name == "linux" and implementation_name == 'cpython')"#).unwrap(); - } - - #[test] - fn error_marker_incomplete1() { - assert_snapshot!( - parse_pep508_err(r"numpy; sys_platform"), - @" - Expected a valid marker operator (such as `>=` or `not in`), found `` - numpy; sys_platform - ^ - " - ); - } - - #[test] - fn error_marker_incomplete2() { - assert_snapshot!( - parse_pep508_err(r"numpy; sys_platform =="), - @r" - Expected marker value, found end of dependency specification - numpy; sys_platform == - ^" - ); - } - - #[test] - fn error_marker_incomplete3() { - assert_snapshot!( - parse_pep508_err(r#"numpy; sys_platform == "win32" or"#), - @r#" - Expected marker value, found end of dependency specification - numpy; sys_platform == "win32" or - ^"# - ); - } - - #[test] - fn error_marker_incomplete4() { - assert_snapshot!( - parse_pep508_err(r#"numpy; sys_platform == "win32" or (os_name == "linux""#), - @r#" - Expected ')', found end of dependency specification - numpy; sys_platform == "win32" or (os_name == "linux" - ^"# - ); - } - - #[test] - fn error_marker_incomplete5() { - assert_snapshot!( - parse_pep508_err(r#"numpy; sys_platform == "win32" or (os_name == "linux" and"#), - @r#" - Expected marker value, found end of dependency specification - numpy; sys_platform == "win32" or (os_name == "linux" and - ^"# - ); - } - - #[test] - fn error_pep440() { - assert_snapshot!( - parse_pep508_err(r"numpy >=1.1.*"), - @r" - Operator >= cannot be used with a wildcard version specifier - numpy >=1.1.* - ^^^^^^^" - ); - } - - #[test] - fn error_no_name() { - assert_snapshot!( - parse_pep508_err(r"==0.0"), - @r" - Expected package name starting with an alphanumeric character, found `=` - ==0.0 - ^ - " - ); - } - - #[test] - fn error_unnamedunnamed_url() { - assert_snapshot!( - parse_pep508_err(r"git+https://github.com/pallets/flask.git"), - @" - URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ https://...`). - git+https://github.com/pallets/flask.git - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" - ); - } - - #[test] - fn error_unnamed_file_path() { - assert_snapshot!( - parse_pep508_err(r"/path/to/flask.tar.gz"), - @r###" - URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ /path/to/file`). - /path/to/flask.tar.gz - ^^^^^^^^^^^^^^^^^^^^^ - "### - ); - } - - #[test] - fn error_no_comma_between_extras() { - assert_snapshot!( - parse_pep508_err(r"name[bar baz]"), - @" - Expected either `,` (separating extras) or `]` (ending the extras section), found `b` - name[bar baz] - ^" - ); - } - - #[test] - fn error_extra_comma_after_extras() { - assert_snapshot!( - parse_pep508_err(r"name[bar, baz,]"), - @" - Expected an alphanumeric character starting the extra name, found `]` - name[bar, baz,] - ^" - ); - } - - #[test] - fn error_extras_not_closed() { - assert_snapshot!( - parse_pep508_err(r"name[bar, baz >= 1.0"), - @" - Expected either `,` (separating extras) or `]` (ending the extras section), found `>` - name[bar, baz >= 1.0 - ^" - ); - } - - #[test] - fn error_no_space_after_url() { - assert_snapshot!( - parse_pep508_err(r"name @ https://example.com/; extra == 'example'"), - @" - Missing space before ';', the end of the URL is ambiguous - name @ https://example.com/; extra == 'example' - ^" - ); - } - - #[test] - fn error_name_at_nothing() { - assert_snapshot!( - parse_pep508_err(r"name @"), - @" - Expected URL - name @ - ^" - ); - } - - #[test] - fn test_error_invalid_marker_key() { - assert_snapshot!( - parse_pep508_err(r"name; invalid_name"), - @" - Expected a quoted string or a valid marker name, found `invalid_name` - name; invalid_name - ^^^^^^^^^^^^ - " - ); - } - - #[test] - fn error_markers_invalid_order() { - assert_snapshot!( - parse_pep508_err("name; '3.7' <= invalid_name"), - @" - Expected a quoted string or a valid marker name, found `invalid_name` - name; '3.7' <= invalid_name - ^^^^^^^^^^^^ - " - ); - } - - #[test] - fn error_markers_notin() { - assert_snapshot!( - parse_pep508_err("name; '3.7' notin python_version"), - @" - Expected a valid marker operator (such as `>=` or `not in`), found `notin` - name; '3.7' notin python_version - ^^^^^" - ); - } - - #[test] - fn error_missing_quote() { - assert_snapshot!( - parse_pep508_err("name; python_version == 3.10"), - @" - Expected a quoted string or a valid marker name, found `3.10` - name; python_version == 3.10 - ^^^^ - " - ); - } - - #[test] - fn error_markers_inpython_version() { - assert_snapshot!( - parse_pep508_err("name; '3.6'inpython_version"), - @" - Expected a valid marker operator (such as `>=` or `not in`), found `inpython_version` - name; '3.6'inpython_version - ^^^^^^^^^^^^^^^^" - ); - } - - #[test] - fn error_markers_not_python_version() { - assert_snapshot!( - parse_pep508_err("name; '3.7' not python_version"), - @" - Expected `i`, found `p` - name; '3.7' not python_version - ^" - ); - } - - #[test] - fn error_markers_invalid_operator() { - assert_snapshot!( - parse_pep508_err("name; '3.7' ~ python_version"), - @" - Expected a valid marker operator (such as `>=` or `not in`), found `~` - name; '3.7' ~ python_version - ^" - ); - } - - #[test] - fn error_invalid_prerelease() { - assert_snapshot!( - parse_pep508_err("name==1.0.org1"), - @r###" - after parsing `1.0`, found `.org1`, which is not part of a valid version - name==1.0.org1 - ^^^^^^^^^^ - "### - ); - } - - #[test] - fn error_no_version_value() { - assert_snapshot!( - parse_pep508_err("name=="), - @" - Unexpected end of version specifier, expected version - name== - ^^" - ); - } - - #[test] - fn error_no_version_operator() { - assert_snapshot!( - parse_pep508_err("name 1.0"), - @" - Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `1` - name 1.0 - ^" - ); - } - - #[test] - fn error_random_char() { - assert_snapshot!( - parse_pep508_err("name >= 1.0 #"), - @" - Trailing `#` is not allowed - name >= 1.0 # - ^^^^^^^^" - ); - } - - #[test] - #[cfg(feature = "non-pep508-extensions")] - fn error_invalid_extra_unnamed_url() { - assert_snapshot!( - parse_unnamed_err("/foo-3.0.0-py3-none-any.whl[d,]"), - @r###" - Expected an alphanumeric character starting the extra name, found `]` - /foo-3.0.0-py3-none-any.whl[d,] - ^ - "### - ); - } - - /// Check that the relative path support feature toggle works. - #[test] - fn non_pep508_paths() { - let requirements = &[ - "foo @ file://./foo", - "foo @ file://foo-3.0.0-py3-none-any.whl", - "foo @ file:foo-3.0.0-py3-none-any.whl", - "foo @ ./foo-3.0.0-py3-none-any.whl", - ]; - let cwd = env::current_dir().unwrap(); - - for requirement in requirements { - assert_eq!( - Requirement::::parse(requirement, &cwd).is_ok(), - cfg!(feature = "non-pep508-extensions"), - "{}: {:?}", - requirement, - Requirement::::parse(requirement, &cwd) - ); - } - } - - #[test] - fn no_space_after_operator() { - let requirement = Requirement::::from_str("pytest;python_version<='4.0'").unwrap(); - assert_eq!( - requirement.to_string(), - "pytest ; python_full_version < '4.1'" - ); - - let requirement = Requirement::::from_str("pytest;'4.0'>=python_version").unwrap(); - assert_eq!( - requirement.to_string(), - "pytest ; python_full_version < '4.1'" - ); - } - - #[test] - fn path_with_fragment() { - let requirements = if cfg!(windows) { - &[ - "wheel @ file:///C:/Users/ferris/wheel-0.42.0.whl#hash=somehash", - "wheel @ C:/Users/ferris/wheel-0.42.0.whl#hash=somehash", - ] - } else { - &[ - "wheel @ file:///Users/ferris/wheel-0.42.0.whl#hash=somehash", - "wheel @ /Users/ferris/wheel-0.42.0.whl#hash=somehash", - ] - }; - - for requirement in requirements { - // Extract the URL. - let Some(VersionOrUrl::Url(url)) = Requirement::::from_str(requirement) - .unwrap() - .version_or_url - else { - unreachable!("Expected a URL") - }; - - // Assert that the fragment and path have been separated correctly. - assert_eq!(url.fragment(), Some("hash=somehash")); - assert!( - url.path().ends_with("/Users/ferris/wheel-0.42.0.whl"), - "Expected the path to end with `/Users/ferris/wheel-0.42.0.whl`, found `{}`", - url.path() - ); - } - } - - #[test] - fn add_extra_marker() -> Result<(), InvalidNameError> { - let requirement = Requirement::::from_str("pytest").unwrap(); - let expected = Requirement::::from_str("pytest; extra == 'dotenv'").unwrap(); - let actual = requirement.with_extra_marker(&ExtraName::from_str("dotenv")?); - assert_eq!(actual, expected); - - let requirement = Requirement::::from_str("pytest; '4.0' >= python_version").unwrap(); - let expected = - Requirement::from_str("pytest; '4.0' >= python_version and extra == 'dotenv'").unwrap(); - let actual = requirement.with_extra_marker(&ExtraName::from_str("dotenv")?); - assert_eq!(actual, expected); - - let requirement = Requirement::::from_str( - "pytest; '4.0' >= python_version or sys_platform == 'win32'", - ) - .unwrap(); - let expected = Requirement::from_str( - "pytest; ('4.0' >= python_version or sys_platform == 'win32') and extra == 'dotenv'", - ) - .unwrap(); - let actual = requirement.with_extra_marker(&ExtraName::from_str("dotenv")?); - assert_eq!(actual, expected); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index e9a68a85b..f44ec54c9 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -860,8 +860,8 @@ impl Edges { low: right_low, }, ) => Edges::Boolean { - high: apply(high.negate(parent), right_high.negate(parent)), - low: apply(low.negate(parent), right_low.negate(parent)), + high: apply(high.negate(parent), right_high.negate(right_parent)), + low: apply(low.negate(parent), right_low.negate(right_parent)), }, _ => unreachable!("cannot merge two `Edges` of different types"), } @@ -959,8 +959,8 @@ impl Edges { low: right_low, }, ) => { - interner.is_disjoint(high.negate(parent), right_high.negate(parent)) - && interner.is_disjoint(low.negate(parent), right_low.negate(parent)) + interner.is_disjoint(high.negate(parent), right_high.negate(right_parent)) + && interner.is_disjoint(low.negate(parent), right_low.negate(right_parent)) } _ => unreachable!("cannot merge two `Edges` of different types"), } @@ -1236,89 +1236,4 @@ impl fmt::Debug for NodeId { } #[cfg(test)] -mod tests { - use super::{NodeId, INTERNER}; - use crate::MarkerExpression; - - fn expr(s: &str) -> NodeId { - INTERNER - .lock() - .expression(MarkerExpression::from_str(s).unwrap().unwrap()) - } - - #[test] - fn basic() { - let m = || INTERNER.lock(); - let extra_foo = expr("extra == 'foo'"); - assert!(!extra_foo.is_false()); - - let os_foo = expr("os_name == 'foo'"); - let extra_and_os_foo = m().or(extra_foo, os_foo); - assert!(!extra_and_os_foo.is_false()); - assert!(!m().and(extra_foo, os_foo).is_false()); - - let trivially_true = m().or(extra_and_os_foo, extra_and_os_foo.not()); - assert!(!trivially_true.is_false()); - assert!(trivially_true.is_true()); - - let trivially_false = m().and(extra_foo, extra_foo.not()); - assert!(trivially_false.is_false()); - - let e = m().or(trivially_false, os_foo); - assert!(!e.is_false()); - - let extra_not_foo = expr("extra != 'foo'"); - assert!(m().and(extra_foo, extra_not_foo).is_false()); - assert!(m().or(extra_foo, extra_not_foo).is_true()); - - let os_geq_bar = expr("os_name >= 'bar'"); - assert!(!os_geq_bar.is_false()); - - let os_le_bar = expr("os_name < 'bar'"); - assert!(m().and(os_geq_bar, os_le_bar).is_false()); - assert!(m().or(os_geq_bar, os_le_bar).is_true()); - - let os_leq_bar = expr("os_name <= 'bar'"); - assert!(!m().and(os_geq_bar, os_leq_bar).is_false()); - assert!(m().or(os_geq_bar, os_leq_bar).is_true()); - } - - #[test] - fn version() { - let m = || INTERNER.lock(); - let eq_3 = expr("python_version == '3'"); - let neq_3 = expr("python_version != '3'"); - let geq_3 = expr("python_version >= '3'"); - let leq_3 = expr("python_version <= '3'"); - - let eq_2 = expr("python_version == '2'"); - let eq_1 = expr("python_version == '1'"); - assert!(m().and(eq_2, eq_1).is_false()); - - assert_eq!(eq_3.not(), neq_3); - assert_eq!(eq_3, neq_3.not()); - - assert!(m().and(eq_3, neq_3).is_false()); - assert!(m().or(eq_3, neq_3).is_true()); - - assert_eq!(m().and(eq_3, geq_3), eq_3); - assert_eq!(m().and(eq_3, leq_3), eq_3); - - assert_eq!(m().and(geq_3, leq_3), eq_3); - - assert!(!m().and(geq_3, leq_3).is_false()); - assert!(m().or(geq_3, leq_3).is_true()); - } - - #[test] - fn simplify() { - let m = || INTERNER.lock(); - let x86 = expr("platform_machine == 'x86_64'"); - let not_x86 = expr("platform_machine != 'x86_64'"); - let windows = expr("platform_machine == 'Windows'"); - - let a = m().and(x86, windows); - let b = m().and(not_x86, windows); - assert_eq!(m().or(a, b), windows); - } -} +mod tests; diff --git a/crates/uv-pep508/src/marker/algebra/tests.rs b/crates/uv-pep508/src/marker/algebra/tests.rs new file mode 100644 index 000000000..00559d4cd --- /dev/null +++ b/crates/uv-pep508/src/marker/algebra/tests.rs @@ -0,0 +1,84 @@ +use super::{NodeId, INTERNER}; +use crate::MarkerExpression; + +fn expr(s: &str) -> NodeId { + INTERNER + .lock() + .expression(MarkerExpression::from_str(s).unwrap().unwrap()) +} + +#[test] +fn basic() { + let m = || INTERNER.lock(); + let extra_foo = expr("extra == 'foo'"); + assert!(!extra_foo.is_false()); + + let os_foo = expr("os_name == 'foo'"); + let extra_and_os_foo = m().or(extra_foo, os_foo); + assert!(!extra_and_os_foo.is_false()); + assert!(!m().and(extra_foo, os_foo).is_false()); + + let trivially_true = m().or(extra_and_os_foo, extra_and_os_foo.not()); + assert!(!trivially_true.is_false()); + assert!(trivially_true.is_true()); + + let trivially_false = m().and(extra_foo, extra_foo.not()); + assert!(trivially_false.is_false()); + + let e = m().or(trivially_false, os_foo); + assert!(!e.is_false()); + + let extra_not_foo = expr("extra != 'foo'"); + assert!(m().and(extra_foo, extra_not_foo).is_false()); + assert!(m().or(extra_foo, extra_not_foo).is_true()); + + let os_geq_bar = expr("os_name >= 'bar'"); + assert!(!os_geq_bar.is_false()); + + let os_le_bar = expr("os_name < 'bar'"); + assert!(m().and(os_geq_bar, os_le_bar).is_false()); + assert!(m().or(os_geq_bar, os_le_bar).is_true()); + + let os_leq_bar = expr("os_name <= 'bar'"); + assert!(!m().and(os_geq_bar, os_leq_bar).is_false()); + assert!(m().or(os_geq_bar, os_leq_bar).is_true()); +} + +#[test] +fn version() { + let m = || INTERNER.lock(); + let eq_3 = expr("python_version == '3'"); + let neq_3 = expr("python_version != '3'"); + let geq_3 = expr("python_version >= '3'"); + let leq_3 = expr("python_version <= '3'"); + + let eq_2 = expr("python_version == '2'"); + let eq_1 = expr("python_version == '1'"); + assert!(m().and(eq_2, eq_1).is_false()); + + assert_eq!(eq_3.not(), neq_3); + assert_eq!(eq_3, neq_3.not()); + + assert!(m().and(eq_3, neq_3).is_false()); + assert!(m().or(eq_3, neq_3).is_true()); + + assert_eq!(m().and(eq_3, geq_3), eq_3); + assert_eq!(m().and(eq_3, leq_3), eq_3); + + assert_eq!(m().and(geq_3, leq_3), eq_3); + + assert!(!m().and(geq_3, leq_3).is_false()); + assert!(m().or(geq_3, leq_3).is_true()); +} + +#[test] +fn simplify() { + let m = || INTERNER.lock(); + let x86 = expr("platform_machine == 'x86_64'"); + let not_x86 = expr("platform_machine != 'x86_64'"); + let windows = expr("platform_machine == 'Windows'"); + + let a = m().and(x86, windows); + let b = m().and(not_x86, windows); + assert_eq!(m().or(a, b), windows); +} diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 55902a69a..677ce6403 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -1238,6 +1238,15 @@ impl MarkerTree { MarkerTreeDebugGraph { marker: self } } + /// Formats a [`MarkerTree`] in its "raw" representation. + /// + /// This is useful for debugging when one wants to look at a + /// representation of a `MarkerTree` that is precisely identical + /// to its internal representation. + pub fn debug_raw(&self) -> MarkerTreeDebugRaw<'_> { + MarkerTreeDebugRaw { marker: self } + } + fn fmt_graph(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { match self.kind() { MarkerTreeKind::True => return write!(f, "true"), @@ -1341,6 +1350,24 @@ impl<'a> fmt::Debug for MarkerTreeDebugGraph<'a> { } } +/// Formats a [`MarkerTree`] using its raw internals. +/// +/// This is very verbose and likely only useful if you're working +/// on the internals of this crate. +/// +/// This type is created by the [`MarkerTree::debug_raw`] routine. +#[derive(Clone)] +pub struct MarkerTreeDebugRaw<'a> { + marker: &'a MarkerTree, +} + +impl<'a> fmt::Debug for MarkerTreeDebugRaw<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let node = INTERNER.shared.node(self.marker.0); + f.debug_tuple("MarkerTreeDebugRaw").field(node).finish() + } +} + /// The underlying kind of an arbitrary node in a [`MarkerTree`]. /// /// A marker tree is represented as an algebraic decision tree with two terminal nodes @@ -2693,6 +2720,41 @@ mod test { assert!(!is_disjoint("'Windows' in os_name", "'Windows' in os_name")); assert!(!is_disjoint("'Linux' in os_name", "os_name not in 'Linux'")); assert!(!is_disjoint("'Linux' not in os_name", "os_name in 'Linux'")); + + assert!(!is_disjoint( + "os_name == 'Linux' and os_name != 'OSX'", + "os_name == 'Linux'" + )); + assert!(is_disjoint( + "os_name == 'Linux' and os_name != 'OSX'", + "os_name == 'OSX'" + )); + + assert!(!is_disjoint( + "extra == 'Linux' and extra != 'OSX'", + "extra == 'Linux'" + )); + assert!(is_disjoint( + "extra == 'Linux' and extra != 'OSX'", + "extra == 'OSX'" + )); + + assert!(!is_disjoint( + "extra == 'x1' and extra != 'x2'", + "extra == 'x1'" + )); + assert!(is_disjoint( + "extra == 'x1' and extra != 'x2'", + "extra == 'x2'" + )); + } + + #[test] + fn is_disjoint_commutative() { + let m1 = m("extra == 'Linux' and extra != 'OSX'"); + let m2 = m("extra == 'Linux'"); + assert!(!m2.is_disjoint(&m1)); + assert!(!m1.is_disjoint(&m2)); } #[test] diff --git a/crates/uv-pep508/src/tests.rs b/crates/uv-pep508/src/tests.rs new file mode 100644 index 000000000..970202ff4 --- /dev/null +++ b/crates/uv-pep508/src/tests.rs @@ -0,0 +1,807 @@ +//! Half of these tests are copied from + +use std::env; +use std::str::FromStr; + +use insta::assert_snapshot; +use url::Url; + +use uv_normalize::{ExtraName, InvalidNameError, PackageName}; +use uv_pep440::{Operator, Version, VersionPattern, VersionSpecifier}; + +use crate::cursor::Cursor; +use crate::marker::{parse, MarkerExpression, MarkerTree, MarkerValueVersion}; +use crate::{ + MarkerOperator, MarkerValueString, Requirement, TracingReporter, VerbatimUrl, VersionOrUrl, +}; + +fn parse_pep508_err(input: &str) -> String { + Requirement::::from_str(input) + .unwrap_err() + .to_string() +} + +#[cfg(feature = "non-pep508-extensions")] +fn parse_unnamed_err(input: &str) -> String { + crate::UnnamedRequirement::::from_str(input) + .unwrap_err() + .to_string() +} + +#[cfg(windows)] +#[test] +fn test_preprocess_url_windows() { + use std::path::PathBuf; + + let actual = crate::parse_url::( + &mut Cursor::new("file:///C:/Users/ferris/wheel-0.42.0.tar.gz"), + None, + ) + .unwrap() + .to_file_path(); + let expected = PathBuf::from(r"C:\Users\ferris\wheel-0.42.0.tar.gz"); + assert_eq!(actual, Ok(expected)); +} + +#[test] +fn error_empty() { + assert_snapshot!( + parse_pep508_err(""), + @r" + Empty field is not allowed for PEP508 + + ^" + ); +} + +#[test] +fn error_start() { + assert_snapshot!( + parse_pep508_err("_name"), + @" + Expected package name starting with an alphanumeric character, found `_` + _name + ^" + ); +} + +#[test] +fn error_end() { + assert_snapshot!( + parse_pep508_err("name_"), + @" + Package name must end with an alphanumeric character, not '_' + name_ + ^" + ); +} + +#[test] +fn basic_examples() { + let input = r"requests[security,tests]==2.8.*,>=2.8.1 ; python_full_version < '2.7'"; + let requests = Requirement::::from_str(input).unwrap(); + assert_eq!(input, requests.to_string()); + let expected = Requirement { + name: PackageName::from_str("requests").unwrap(), + extras: vec![ + ExtraName::from_str("security").unwrap(), + ExtraName::from_str("tests").unwrap(), + ], + version_or_url: Some(VersionOrUrl::VersionSpecifier( + [ + VersionSpecifier::from_pattern( + Operator::Equal, + VersionPattern::wildcard(Version::new([2, 8])), + ) + .unwrap(), + VersionSpecifier::from_pattern( + Operator::GreaterThanEqual, + VersionPattern::verbatim(Version::new([2, 8, 1])), + ) + .unwrap(), + ] + .into_iter() + .collect(), + )), + marker: MarkerTree::expression(MarkerExpression::Version { + key: MarkerValueVersion::PythonFullVersion, + specifier: VersionSpecifier::from_pattern( + uv_pep440::Operator::LessThan, + "2.7".parse().unwrap(), + ) + .unwrap(), + }), + origin: None, + }; + assert_eq!(requests, expected); +} + +#[test] +fn leading_whitespace() { + let numpy = Requirement::::from_str(" numpy").unwrap(); + assert_eq!(numpy.name.as_ref(), "numpy"); +} + +#[test] +fn parenthesized_single() { + let numpy = Requirement::::from_str("numpy ( >=1.19 )").unwrap(); + assert_eq!(numpy.name.as_ref(), "numpy"); +} + +#[test] +fn parenthesized_double() { + let numpy = Requirement::::from_str("numpy ( >=1.19, <2.0 )").unwrap(); + assert_eq!(numpy.name.as_ref(), "numpy"); +} + +#[test] +fn versions_single() { + let numpy = Requirement::::from_str("numpy >=1.19 ").unwrap(); + assert_eq!(numpy.name.as_ref(), "numpy"); +} + +#[test] +fn versions_double() { + let numpy = Requirement::::from_str("numpy >=1.19, <2.0 ").unwrap(); + assert_eq!(numpy.name.as_ref(), "numpy"); +} + +#[test] +#[cfg(feature = "non-pep508-extensions")] +fn direct_url_no_extras() { + let numpy = crate::UnnamedRequirement::::from_str("https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl").unwrap(); + assert_eq!(numpy.url.to_string(), "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl"); + assert_eq!(numpy.extras, vec![]); +} + +#[test] +#[cfg(all(unix, feature = "non-pep508-extensions"))] +fn direct_url_extras() { + let numpy = crate::UnnamedRequirement::::from_str( + "/path/to/numpy-1.26.4-cp312-cp312-win32.whl[dev]", + ) + .unwrap(); + assert_eq!( + numpy.url.to_string(), + "file:///path/to/numpy-1.26.4-cp312-cp312-win32.whl" + ); + assert_eq!(numpy.extras, vec![ExtraName::from_str("dev").unwrap()]); +} + +#[test] +#[cfg(all(windows, feature = "non-pep508-extensions"))] +fn direct_url_extras() { + let numpy = crate::UnnamedRequirement::::from_str( + "C:\\path\\to\\numpy-1.26.4-cp312-cp312-win32.whl[dev]", + ) + .unwrap(); + assert_eq!( + numpy.url.to_string(), + "file:///C:/path/to/numpy-1.26.4-cp312-cp312-win32.whl" + ); + assert_eq!(numpy.extras, vec![ExtraName::from_str("dev").unwrap()]); +} + +#[test] +fn error_extras_eof1() { + assert_snapshot!( + parse_pep508_err("black["), + @r#" + Missing closing bracket (expected ']', found end of dependency specification) + black[ + ^ + "# + ); +} + +#[test] +fn error_extras_eof2() { + assert_snapshot!( + parse_pep508_err("black[d"), + @r#" + Missing closing bracket (expected ']', found end of dependency specification) + black[d + ^ + "# + ); +} + +#[test] +fn error_extras_eof3() { + assert_snapshot!( + parse_pep508_err("black[d,"), + @r#" + Missing closing bracket (expected ']', found end of dependency specification) + black[d, + ^ + "# + ); +} + +#[test] +fn error_extras_illegal_start1() { + assert_snapshot!( + parse_pep508_err("black[ö]"), + @r#" + Expected an alphanumeric character starting the extra name, found `ö` + black[ö] + ^ + "# + ); +} + +#[test] +fn error_extras_illegal_start2() { + assert_snapshot!( + parse_pep508_err("black[_d]"), + @r#" + Expected an alphanumeric character starting the extra name, found `_` + black[_d] + ^ + "# + ); +} + +#[test] +fn error_extras_illegal_start3() { + assert_snapshot!( + parse_pep508_err("black[,]"), + @r#" + Expected either alphanumerical character (starting the extra name) or `]` (ending the extras section), found `,` + black[,] + ^ + "# + ); +} + +#[test] +fn error_extras_illegal_character() { + assert_snapshot!( + parse_pep508_err("black[jüpyter]"), + @r#" + Invalid character in extras name, expected an alphanumeric character, `-`, `_`, `.`, `,` or `]`, found `ü` + black[jüpyter] + ^ + "# + ); +} + +#[test] +fn error_extras1() { + let numpy = Requirement::::from_str("black[d]").unwrap(); + assert_eq!(numpy.extras, vec![ExtraName::from_str("d").unwrap()]); +} + +#[test] +fn error_extras2() { + let numpy = Requirement::::from_str("black[d,jupyter]").unwrap(); + assert_eq!( + numpy.extras, + vec![ + ExtraName::from_str("d").unwrap(), + ExtraName::from_str("jupyter").unwrap(), + ] + ); +} + +#[test] +fn empty_extras() { + let black = Requirement::::from_str("black[]").unwrap(); + assert_eq!(black.extras, vec![]); +} + +#[test] +fn empty_extras_with_spaces() { + let black = Requirement::::from_str("black[ ]").unwrap(); + assert_eq!(black.extras, vec![]); +} + +#[test] +fn error_extra_with_trailing_comma() { + assert_snapshot!( + parse_pep508_err("black[d,]"), + @" + Expected an alphanumeric character starting the extra name, found `]` + black[d,] + ^" + ); +} + +#[test] +fn error_parenthesized_pep440() { + assert_snapshot!( + parse_pep508_err("numpy ( ><1.19 )"), + @" + no such comparison operator \"><\", must be one of ~= == != <= >= < > === + numpy ( ><1.19 ) + ^^^^^^^" + ); +} + +#[test] +fn error_parenthesized_parenthesis() { + assert_snapshot!( + parse_pep508_err("numpy ( >=1.19"), + @r#" + Missing closing parenthesis (expected ')', found end of dependency specification) + numpy ( >=1.19 + ^ + "# + ); +} + +#[test] +fn error_whats_that() { + assert_snapshot!( + parse_pep508_err("numpy % 1.16"), + @r#" + Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `%` + numpy % 1.16 + ^ + "# + ); +} + +#[test] +fn url() { + let pip_url = + Requirement::from_str("pip @ https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686") + .unwrap(); + let url = "https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686"; + let expected = Requirement { + name: PackageName::from_str("pip").unwrap(), + extras: vec![], + marker: MarkerTree::TRUE, + version_or_url: Some(VersionOrUrl::Url(Url::parse(url).unwrap())), + origin: None, + }; + assert_eq!(pip_url, expected); +} + +#[test] +fn test_marker_parsing() { + let marker = r#"python_version == "2.7" and (sys_platform == "win32" or (os_name == "linux" and implementation_name == 'cpython'))"#; + let actual = + parse::parse_markers_cursor::(&mut Cursor::new(marker), &mut TracingReporter) + .unwrap() + .unwrap(); + + let mut a = MarkerTree::expression(MarkerExpression::Version { + key: MarkerValueVersion::PythonVersion, + specifier: VersionSpecifier::from_pattern( + uv_pep440::Operator::Equal, + "2.7".parse().unwrap(), + ) + .unwrap(), + }); + let mut b = MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::SysPlatform, + operator: MarkerOperator::Equal, + value: "win32".to_string(), + }); + let mut c = MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::OsName, + operator: MarkerOperator::Equal, + value: "linux".to_string(), + }); + let d = MarkerTree::expression(MarkerExpression::String { + key: MarkerValueString::ImplementationName, + operator: MarkerOperator::Equal, + value: "cpython".to_string(), + }); + + c.and(d); + b.or(c); + a.and(b); + + assert_eq!(a, actual); +} + +#[test] +fn name_and_marker() { + Requirement::::from_str(r#"numpy; sys_platform == "win32" or (os_name == "linux" and implementation_name == 'cpython')"#).unwrap(); +} + +#[test] +fn error_marker_incomplete1() { + assert_snapshot!( + parse_pep508_err(r"numpy; sys_platform"), + @r#" + Expected a valid marker operator (such as `>=` or `not in`), found `` + numpy; sys_platform + ^ + "# + ); +} + +#[test] +fn error_marker_incomplete2() { + assert_snapshot!( + parse_pep508_err(r"numpy; sys_platform =="), + @r#" + Expected marker value, found end of dependency specification + numpy; sys_platform == + ^ + "# + ); +} + +#[test] +fn error_marker_incomplete3() { + assert_snapshot!( + parse_pep508_err(r#"numpy; sys_platform == "win32" or"#), + @r#" + Expected marker value, found end of dependency specification + numpy; sys_platform == "win32" or + ^ + "# + ); +} + +#[test] +fn error_marker_incomplete4() { + assert_snapshot!( + parse_pep508_err(r#"numpy; sys_platform == "win32" or (os_name == "linux""#), + @r#" + Expected ')', found end of dependency specification + numpy; sys_platform == "win32" or (os_name == "linux" + ^ + "# + ); +} + +#[test] +fn error_marker_incomplete5() { + assert_snapshot!( + parse_pep508_err(r#"numpy; sys_platform == "win32" or (os_name == "linux" and"#), + @r#" + Expected marker value, found end of dependency specification + numpy; sys_platform == "win32" or (os_name == "linux" and + ^ + "# + ); +} + +#[test] +fn error_pep440() { + assert_snapshot!( + parse_pep508_err(r"numpy >=1.1.*"), + @r#" + Operator >= cannot be used with a wildcard version specifier + numpy >=1.1.* + ^^^^^^^ + "# + ); +} + +#[test] +fn error_no_name() { + assert_snapshot!( + parse_pep508_err(r"==0.0"), + @r" + Expected package name starting with an alphanumeric character, found `=` + ==0.0 + ^ + " + ); +} + +#[test] +fn error_unnamedunnamed_url() { + assert_snapshot!( + parse_pep508_err(r"git+https://github.com/pallets/flask.git"), + @" + URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ https://...`). + git+https://github.com/pallets/flask.git + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + ); +} + +#[test] +fn error_unnamed_file_path() { + assert_snapshot!( + parse_pep508_err(r"/path/to/flask.tar.gz"), + @r###" + URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ /path/to/file`). + /path/to/flask.tar.gz + ^^^^^^^^^^^^^^^^^^^^^ + "### + ); +} + +#[test] +fn error_no_comma_between_extras() { + assert_snapshot!( + parse_pep508_err(r"name[bar baz]"), + @r#" + Expected either `,` (separating extras) or `]` (ending the extras section), found `b` + name[bar baz] + ^ + "# + ); +} + +#[test] +fn error_extra_comma_after_extras() { + assert_snapshot!( + parse_pep508_err(r"name[bar, baz,]"), + @r#" + Expected an alphanumeric character starting the extra name, found `]` + name[bar, baz,] + ^ + "# + ); +} + +#[test] +fn error_extras_not_closed() { + assert_snapshot!( + parse_pep508_err(r"name[bar, baz >= 1.0"), + @r#" + Expected either `,` (separating extras) or `]` (ending the extras section), found `>` + name[bar, baz >= 1.0 + ^ + "# + ); +} + +#[test] +fn error_no_space_after_url() { + assert_snapshot!( + parse_pep508_err(r"name @ https://example.com/; extra == 'example'"), + @r#" + Missing space before ';', the end of the URL is ambiguous + name @ https://example.com/; extra == 'example' + ^ + "# + ); +} + +#[test] +fn error_name_at_nothing() { + assert_snapshot!( + parse_pep508_err(r"name @"), + @r#" + Expected URL + name @ + ^ + "# + ); +} + +#[test] +fn test_error_invalid_marker_key() { + assert_snapshot!( + parse_pep508_err(r"name; invalid_name"), + @r#" + Expected a quoted string or a valid marker name, found `invalid_name` + name; invalid_name + ^^^^^^^^^^^^ + "# + ); +} + +#[test] +fn error_markers_invalid_order() { + assert_snapshot!( + parse_pep508_err("name; '3.7' <= invalid_name"), + @r#" + Expected a quoted string or a valid marker name, found `invalid_name` + name; '3.7' <= invalid_name + ^^^^^^^^^^^^ + "# + ); +} + +#[test] +fn error_markers_notin() { + assert_snapshot!( + parse_pep508_err("name; '3.7' notin python_version"), + @" + Expected a valid marker operator (such as `>=` or `not in`), found `notin` + name; '3.7' notin python_version + ^^^^^" + ); +} + +#[test] +fn error_missing_quote() { + assert_snapshot!( + parse_pep508_err("name; python_version == 3.10"), + @" + Expected a quoted string or a valid marker name, found `3.10` + name; python_version == 3.10 + ^^^^ + " + ); +} + +#[test] +fn error_markers_inpython_version() { + assert_snapshot!( + parse_pep508_err("name; '3.6'inpython_version"), + @r#" + Expected a valid marker operator (such as `>=` or `not in`), found `inpython_version` + name; '3.6'inpython_version + ^^^^^^^^^^^^^^^^ + "# + ); +} + +#[test] +fn error_markers_not_python_version() { + assert_snapshot!( + parse_pep508_err("name; '3.7' not python_version"), + @" + Expected `i`, found `p` + name; '3.7' not python_version + ^" + ); +} + +#[test] +fn error_markers_invalid_operator() { + assert_snapshot!( + parse_pep508_err("name; '3.7' ~ python_version"), + @" + Expected a valid marker operator (such as `>=` or `not in`), found `~` + name; '3.7' ~ python_version + ^" + ); +} + +#[test] +fn error_invalid_prerelease() { + assert_snapshot!( + parse_pep508_err("name==1.0.org1"), + @r###" + after parsing `1.0`, found `.org1`, which is not part of a valid version + name==1.0.org1 + ^^^^^^^^^^ + "### + ); +} + +#[test] +fn error_no_version_value() { + assert_snapshot!( + parse_pep508_err("name=="), + @" + Unexpected end of version specifier, expected version + name== + ^^" + ); +} + +#[test] +fn error_no_version_operator() { + assert_snapshot!( + parse_pep508_err("name 1.0"), + @r#" + Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `1` + name 1.0 + ^ + "# + ); +} + +#[test] +fn error_random_char() { + assert_snapshot!( + parse_pep508_err("name >= 1.0 #"), + @r##" + Trailing `#` is not allowed + name >= 1.0 # + ^^^^^^^^ + "## + ); +} + +#[test] +#[cfg(feature = "non-pep508-extensions")] +fn error_invalid_extra_unnamed_url() { + assert_snapshot!( + parse_unnamed_err("/foo-3.0.0-py3-none-any.whl[d,]"), + @r#" + Expected an alphanumeric character starting the extra name, found `]` + /foo-3.0.0-py3-none-any.whl[d,] + ^ + "# + ); +} + +/// Check that the relative path support feature toggle works. +#[test] +fn non_pep508_paths() { + let requirements = &[ + "foo @ file://./foo", + "foo @ file://foo-3.0.0-py3-none-any.whl", + "foo @ file:foo-3.0.0-py3-none-any.whl", + "foo @ ./foo-3.0.0-py3-none-any.whl", + ]; + let cwd = env::current_dir().unwrap(); + + for requirement in requirements { + assert_eq!( + Requirement::::parse(requirement, &cwd).is_ok(), + cfg!(feature = "non-pep508-extensions"), + "{}: {:?}", + requirement, + Requirement::::parse(requirement, &cwd) + ); + } +} + +#[test] +fn no_space_after_operator() { + let requirement = Requirement::::from_str("pytest;python_version<='4.0'").unwrap(); + assert_eq!( + requirement.to_string(), + "pytest ; python_full_version < '4.1'" + ); + + let requirement = Requirement::::from_str("pytest;'4.0'>=python_version").unwrap(); + assert_eq!( + requirement.to_string(), + "pytest ; python_full_version < '4.1'" + ); +} + +#[test] +fn path_with_fragment() { + let requirements = if cfg!(windows) { + &[ + "wheel @ file:///C:/Users/ferris/wheel-0.42.0.whl#hash=somehash", + "wheel @ C:/Users/ferris/wheel-0.42.0.whl#hash=somehash", + ] + } else { + &[ + "wheel @ file:///Users/ferris/wheel-0.42.0.whl#hash=somehash", + "wheel @ /Users/ferris/wheel-0.42.0.whl#hash=somehash", + ] + }; + + for requirement in requirements { + // Extract the URL. + let Some(VersionOrUrl::Url(url)) = Requirement::::from_str(requirement) + .unwrap() + .version_or_url + else { + unreachable!("Expected a URL") + }; + + // Assert that the fragment and path have been separated correctly. + assert_eq!(url.fragment(), Some("hash=somehash")); + assert!( + url.path().ends_with("/Users/ferris/wheel-0.42.0.whl"), + "Expected the path to end with `/Users/ferris/wheel-0.42.0.whl`, found `{}`", + url.path() + ); + } +} + +#[test] +fn add_extra_marker() -> Result<(), InvalidNameError> { + let requirement = Requirement::::from_str("pytest").unwrap(); + let expected = Requirement::::from_str("pytest; extra == 'dotenv'").unwrap(); + let actual = requirement.with_extra_marker(&ExtraName::from_str("dotenv")?); + assert_eq!(actual, expected); + + let requirement = Requirement::::from_str("pytest; '4.0' >= python_version").unwrap(); + let expected = + Requirement::from_str("pytest; '4.0' >= python_version and extra == 'dotenv'").unwrap(); + let actual = requirement.with_extra_marker(&ExtraName::from_str("dotenv")?); + assert_eq!(actual, expected); + + let requirement = + Requirement::::from_str("pytest; '4.0' >= python_version or sys_platform == 'win32'") + .unwrap(); + let expected = Requirement::from_str( + "pytest; ('4.0' >= python_version or sys_platform == 'win32') and extra == 'dotenv'", + ) + .unwrap(); + let actual = requirement.with_extra_marker(&ExtraName::from_str("dotenv")?); + assert_eq!(actual, expected); + + Ok(()) +} diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index 9445b13cf..f75539d2e 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -134,6 +134,11 @@ impl VerbatimUrl { self.url.clone() } + /// Convert a [`VerbatimUrl`] into a [`Url`]. + pub fn into_url(self) -> Url { + self.url + } + /// Return the underlying [`Path`], if the URL is a file URL. pub fn as_path(&self) -> Result { self.url @@ -516,61 +521,4 @@ impl std::fmt::Display for Scheme { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn scheme() { - assert_eq!( - split_scheme("file:///home/ferris/project/scripts"), - Some(("file", "///home/ferris/project/scripts")) - ); - assert_eq!( - split_scheme("file:home/ferris/project/scripts"), - Some(("file", "home/ferris/project/scripts")) - ); - assert_eq!( - split_scheme("https://example.com"), - Some(("https", "//example.com")) - ); - assert_eq!(split_scheme("https:"), Some(("https", ""))); - } - - #[test] - fn fragment() { - assert_eq!( - split_fragment(Path::new( - "file:///home/ferris/project/scripts#hash=somehash" - )), - ( - Cow::Owned(PathBuf::from("file:///home/ferris/project/scripts")), - Some("hash=somehash") - ) - ); - assert_eq!( - split_fragment(Path::new("file:home/ferris/project/scripts#hash=somehash")), - ( - Cow::Owned(PathBuf::from("file:home/ferris/project/scripts")), - Some("hash=somehash") - ) - ); - assert_eq!( - split_fragment(Path::new("/home/ferris/project/scripts#hash=somehash")), - ( - Cow::Owned(PathBuf::from("/home/ferris/project/scripts")), - Some("hash=somehash") - ) - ); - assert_eq!( - split_fragment(Path::new("file:///home/ferris/project/scripts")), - ( - Cow::Borrowed(Path::new("file:///home/ferris/project/scripts")), - None - ) - ); - assert_eq!( - split_fragment(Path::new("")), - (Cow::Borrowed(Path::new("")), None) - ); - } -} +mod tests; diff --git a/crates/uv-pep508/src/verbatim_url/tests.rs b/crates/uv-pep508/src/verbatim_url/tests.rs new file mode 100644 index 000000000..5706d0394 --- /dev/null +++ b/crates/uv-pep508/src/verbatim_url/tests.rs @@ -0,0 +1,56 @@ +use super::*; + +#[test] +fn scheme() { + assert_eq!( + split_scheme("file:///home/ferris/project/scripts"), + Some(("file", "///home/ferris/project/scripts")) + ); + assert_eq!( + split_scheme("file:home/ferris/project/scripts"), + Some(("file", "home/ferris/project/scripts")) + ); + assert_eq!( + split_scheme("https://example.com"), + Some(("https", "//example.com")) + ); + assert_eq!(split_scheme("https:"), Some(("https", ""))); +} + +#[test] +fn fragment() { + assert_eq!( + split_fragment(Path::new( + "file:///home/ferris/project/scripts#hash=somehash" + )), + ( + Cow::Owned(PathBuf::from("file:///home/ferris/project/scripts")), + Some("hash=somehash") + ) + ); + assert_eq!( + split_fragment(Path::new("file:home/ferris/project/scripts#hash=somehash")), + ( + Cow::Owned(PathBuf::from("file:home/ferris/project/scripts")), + Some("hash=somehash") + ) + ); + assert_eq!( + split_fragment(Path::new("/home/ferris/project/scripts#hash=somehash")), + ( + Cow::Owned(PathBuf::from("/home/ferris/project/scripts")), + Some("hash=somehash") + ) + ); + assert_eq!( + split_fragment(Path::new("file:///home/ferris/project/scripts")), + ( + Cow::Borrowed(Path::new("file:///home/ferris/project/scripts")), + None + ) + ); + assert_eq!( + split_fragment(Path::new("")), + (Cow::Borrowed(Path::new("")), None) + ); +} diff --git a/crates/uv-performance-flate2-backend/Cargo.toml b/crates/uv-performance-flate2-backend/Cargo.toml index 5dba55cc8..df9e4190b 100644 --- a/crates/uv-performance-flate2-backend/Cargo.toml +++ b/crates/uv-performance-flate2-backend/Cargo.toml @@ -3,6 +3,9 @@ name = "uv-performance-flate2-backend" version = "0.1.0" publish = false +[lib] +doctest = false + [target.'cfg(not(any(target_arch = "s390x", target_arch = "powerpc64")))'.dependencies] flate2 = { version = "1.0.28", default-features = false, features = ["zlib-ng"] } diff --git a/crates/uv-performance-memory-allocator/Cargo.toml b/crates/uv-performance-memory-allocator/Cargo.toml index 50d2a149a..173072875 100644 --- a/crates/uv-performance-memory-allocator/Cargo.toml +++ b/crates/uv-performance-memory-allocator/Cargo.toml @@ -3,6 +3,9 @@ name = "uv-performance-memory-allocator" version = "0.1.0" publish = false +[lib] +doctest = false + [dependencies] [target.'cfg(all(target_os = "windows"))'.dependencies] diff --git a/crates/uv-platform-tags/Cargo.toml b/crates/uv-platform-tags/Cargo.toml index 0879e62ae..add9e4856 100644 --- a/crates/uv-platform-tags/Cargo.toml +++ b/crates/uv-platform-tags/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-platform-tags/src/tags.rs b/crates/uv-platform-tags/src/tags.rs index 67ab80357..ae4f702d5 100644 --- a/crates/uv-platform-tags/src/tags.rs +++ b/crates/uv-platform-tags/src/tags.rs @@ -574,1535 +574,4 @@ fn get_mac_binary_formats(arch: Arch) -> Vec { } #[cfg(test)] -mod tests { - use insta::{assert_debug_snapshot, assert_snapshot}; - - use super::*; - - /// Check platform tag ordering. - /// The list is displayed in decreasing priority. - /// - /// A reference list can be generated with: - /// ```text - /// $ python -c "from packaging import tags; [print(tag) for tag in tags.platform_tags()]"` - /// ```` - #[test] - fn test_platform_tags_manylinux() { - let tags = compatible_tags(&Platform::new( - Os::Manylinux { - major: 2, - minor: 20, - }, - Arch::X86_64, - )) - .unwrap(); - assert_debug_snapshot!( - tags, - @r###" - [ - "manylinux_2_20_x86_64", - "manylinux_2_19_x86_64", - "manylinux_2_18_x86_64", - "manylinux_2_17_x86_64", - "manylinux2014_x86_64", - "manylinux_2_16_x86_64", - "manylinux_2_15_x86_64", - "manylinux_2_14_x86_64", - "manylinux_2_13_x86_64", - "manylinux_2_12_x86_64", - "manylinux2010_x86_64", - "manylinux_2_11_x86_64", - "manylinux_2_10_x86_64", - "manylinux_2_9_x86_64", - "manylinux_2_8_x86_64", - "manylinux_2_7_x86_64", - "manylinux_2_6_x86_64", - "manylinux_2_5_x86_64", - "manylinux1_x86_64", - "linux_x86_64", - ] - "### - ); - } - - #[test] - fn test_platform_tags_macos() { - let tags = compatible_tags(&Platform::new( - Os::Macos { - major: 21, - minor: 6, - }, - Arch::X86_64, - )) - .unwrap(); - assert_debug_snapshot!( - tags, - @r###" - [ - "macosx_21_0_x86_64", - "macosx_21_0_intel", - "macosx_21_0_fat64", - "macosx_21_0_fat32", - "macosx_21_0_universal2", - "macosx_21_0_universal", - "macosx_20_0_x86_64", - "macosx_20_0_intel", - "macosx_20_0_fat64", - "macosx_20_0_fat32", - "macosx_20_0_universal2", - "macosx_20_0_universal", - "macosx_19_0_x86_64", - "macosx_19_0_intel", - "macosx_19_0_fat64", - "macosx_19_0_fat32", - "macosx_19_0_universal2", - "macosx_19_0_universal", - "macosx_18_0_x86_64", - "macosx_18_0_intel", - "macosx_18_0_fat64", - "macosx_18_0_fat32", - "macosx_18_0_universal2", - "macosx_18_0_universal", - "macosx_17_0_x86_64", - "macosx_17_0_intel", - "macosx_17_0_fat64", - "macosx_17_0_fat32", - "macosx_17_0_universal2", - "macosx_17_0_universal", - "macosx_16_0_x86_64", - "macosx_16_0_intel", - "macosx_16_0_fat64", - "macosx_16_0_fat32", - "macosx_16_0_universal2", - "macosx_16_0_universal", - "macosx_15_0_x86_64", - "macosx_15_0_intel", - "macosx_15_0_fat64", - "macosx_15_0_fat32", - "macosx_15_0_universal2", - "macosx_15_0_universal", - "macosx_14_0_x86_64", - "macosx_14_0_intel", - "macosx_14_0_fat64", - "macosx_14_0_fat32", - "macosx_14_0_universal2", - "macosx_14_0_universal", - "macosx_13_0_x86_64", - "macosx_13_0_intel", - "macosx_13_0_fat64", - "macosx_13_0_fat32", - "macosx_13_0_universal2", - "macosx_13_0_universal", - "macosx_12_0_x86_64", - "macosx_12_0_intel", - "macosx_12_0_fat64", - "macosx_12_0_fat32", - "macosx_12_0_universal2", - "macosx_12_0_universal", - "macosx_11_0_x86_64", - "macosx_11_0_intel", - "macosx_11_0_fat64", - "macosx_11_0_fat32", - "macosx_11_0_universal2", - "macosx_11_0_universal", - "macosx_10_16_x86_64", - "macosx_10_16_intel", - "macosx_10_16_fat64", - "macosx_10_16_fat32", - "macosx_10_16_universal2", - "macosx_10_16_universal", - "macosx_10_15_x86_64", - "macosx_10_15_intel", - "macosx_10_15_fat64", - "macosx_10_15_fat32", - "macosx_10_15_universal2", - "macosx_10_15_universal", - "macosx_10_14_x86_64", - "macosx_10_14_intel", - "macosx_10_14_fat64", - "macosx_10_14_fat32", - "macosx_10_14_universal2", - "macosx_10_14_universal", - "macosx_10_13_x86_64", - "macosx_10_13_intel", - "macosx_10_13_fat64", - "macosx_10_13_fat32", - "macosx_10_13_universal2", - "macosx_10_13_universal", - "macosx_10_12_x86_64", - "macosx_10_12_intel", - "macosx_10_12_fat64", - "macosx_10_12_fat32", - "macosx_10_12_universal2", - "macosx_10_12_universal", - "macosx_10_11_x86_64", - "macosx_10_11_intel", - "macosx_10_11_fat64", - "macosx_10_11_fat32", - "macosx_10_11_universal2", - "macosx_10_11_universal", - "macosx_10_10_x86_64", - "macosx_10_10_intel", - "macosx_10_10_fat64", - "macosx_10_10_fat32", - "macosx_10_10_universal2", - "macosx_10_10_universal", - "macosx_10_9_x86_64", - "macosx_10_9_intel", - "macosx_10_9_fat64", - "macosx_10_9_fat32", - "macosx_10_9_universal2", - "macosx_10_9_universal", - "macosx_10_8_x86_64", - "macosx_10_8_intel", - "macosx_10_8_fat64", - "macosx_10_8_fat32", - "macosx_10_8_universal2", - "macosx_10_8_universal", - "macosx_10_7_x86_64", - "macosx_10_7_intel", - "macosx_10_7_fat64", - "macosx_10_7_fat32", - "macosx_10_7_universal2", - "macosx_10_7_universal", - "macosx_10_6_x86_64", - "macosx_10_6_intel", - "macosx_10_6_fat64", - "macosx_10_6_fat32", - "macosx_10_6_universal2", - "macosx_10_6_universal", - "macosx_10_5_x86_64", - "macosx_10_5_intel", - "macosx_10_5_fat64", - "macosx_10_5_fat32", - "macosx_10_5_universal2", - "macosx_10_5_universal", - "macosx_10_4_x86_64", - "macosx_10_4_intel", - "macosx_10_4_fat64", - "macosx_10_4_fat32", - "macosx_10_4_universal2", - "macosx_10_4_universal", - ] - "### - ); - - let tags = compatible_tags(&Platform::new( - Os::Macos { - major: 14, - minor: 0, - }, - Arch::X86_64, - )) - .unwrap(); - assert_debug_snapshot!( - tags, - @r###" - [ - "macosx_14_0_x86_64", - "macosx_14_0_intel", - "macosx_14_0_fat64", - "macosx_14_0_fat32", - "macosx_14_0_universal2", - "macosx_14_0_universal", - "macosx_13_0_x86_64", - "macosx_13_0_intel", - "macosx_13_0_fat64", - "macosx_13_0_fat32", - "macosx_13_0_universal2", - "macosx_13_0_universal", - "macosx_12_0_x86_64", - "macosx_12_0_intel", - "macosx_12_0_fat64", - "macosx_12_0_fat32", - "macosx_12_0_universal2", - "macosx_12_0_universal", - "macosx_11_0_x86_64", - "macosx_11_0_intel", - "macosx_11_0_fat64", - "macosx_11_0_fat32", - "macosx_11_0_universal2", - "macosx_11_0_universal", - "macosx_10_16_x86_64", - "macosx_10_16_intel", - "macosx_10_16_fat64", - "macosx_10_16_fat32", - "macosx_10_16_universal2", - "macosx_10_16_universal", - "macosx_10_15_x86_64", - "macosx_10_15_intel", - "macosx_10_15_fat64", - "macosx_10_15_fat32", - "macosx_10_15_universal2", - "macosx_10_15_universal", - "macosx_10_14_x86_64", - "macosx_10_14_intel", - "macosx_10_14_fat64", - "macosx_10_14_fat32", - "macosx_10_14_universal2", - "macosx_10_14_universal", - "macosx_10_13_x86_64", - "macosx_10_13_intel", - "macosx_10_13_fat64", - "macosx_10_13_fat32", - "macosx_10_13_universal2", - "macosx_10_13_universal", - "macosx_10_12_x86_64", - "macosx_10_12_intel", - "macosx_10_12_fat64", - "macosx_10_12_fat32", - "macosx_10_12_universal2", - "macosx_10_12_universal", - "macosx_10_11_x86_64", - "macosx_10_11_intel", - "macosx_10_11_fat64", - "macosx_10_11_fat32", - "macosx_10_11_universal2", - "macosx_10_11_universal", - "macosx_10_10_x86_64", - "macosx_10_10_intel", - "macosx_10_10_fat64", - "macosx_10_10_fat32", - "macosx_10_10_universal2", - "macosx_10_10_universal", - "macosx_10_9_x86_64", - "macosx_10_9_intel", - "macosx_10_9_fat64", - "macosx_10_9_fat32", - "macosx_10_9_universal2", - "macosx_10_9_universal", - "macosx_10_8_x86_64", - "macosx_10_8_intel", - "macosx_10_8_fat64", - "macosx_10_8_fat32", - "macosx_10_8_universal2", - "macosx_10_8_universal", - "macosx_10_7_x86_64", - "macosx_10_7_intel", - "macosx_10_7_fat64", - "macosx_10_7_fat32", - "macosx_10_7_universal2", - "macosx_10_7_universal", - "macosx_10_6_x86_64", - "macosx_10_6_intel", - "macosx_10_6_fat64", - "macosx_10_6_fat32", - "macosx_10_6_universal2", - "macosx_10_6_universal", - "macosx_10_5_x86_64", - "macosx_10_5_intel", - "macosx_10_5_fat64", - "macosx_10_5_fat32", - "macosx_10_5_universal2", - "macosx_10_5_universal", - "macosx_10_4_x86_64", - "macosx_10_4_intel", - "macosx_10_4_fat64", - "macosx_10_4_fat32", - "macosx_10_4_universal2", - "macosx_10_4_universal", - ] - "### - ); - - let tags = compatible_tags(&Platform::new( - Os::Macos { - major: 10, - minor: 6, - }, - Arch::X86_64, - )) - .unwrap(); - assert_debug_snapshot!( - tags, - @r###" - [ - "macosx_10_6_x86_64", - "macosx_10_6_intel", - "macosx_10_6_fat64", - "macosx_10_6_fat32", - "macosx_10_6_universal2", - "macosx_10_6_universal", - "macosx_10_5_x86_64", - "macosx_10_5_intel", - "macosx_10_5_fat64", - "macosx_10_5_fat32", - "macosx_10_5_universal2", - "macosx_10_5_universal", - "macosx_10_4_x86_64", - "macosx_10_4_intel", - "macosx_10_4_fat64", - "macosx_10_4_fat32", - "macosx_10_4_universal2", - "macosx_10_4_universal", - ] - "### - ); - } - - /// Ensure the tags returned do not include the `manylinux` tags - /// when `manylinux_incompatible` is set to `false`. - #[test] - fn test_manylinux_incompatible() { - let tags = Tags::from_env( - &Platform::new( - Os::Manylinux { - major: 2, - minor: 28, - }, - Arch::X86_64, - ), - (3, 9), - "cpython", - (3, 9), - false, - false, - ) - .unwrap(); - assert_snapshot!( - tags, - @r###" - cp39-cp39-linux_x86_64 - cp39-abi3-linux_x86_64 - cp39-none-linux_x86_64 - cp38-abi3-linux_x86_64 - cp37-abi3-linux_x86_64 - cp36-abi3-linux_x86_64 - cp35-abi3-linux_x86_64 - cp34-abi3-linux_x86_64 - cp33-abi3-linux_x86_64 - cp32-abi3-linux_x86_64 - py39-none-linux_x86_64 - py3-none-linux_x86_64 - py38-none-linux_x86_64 - py37-none-linux_x86_64 - py36-none-linux_x86_64 - py35-none-linux_x86_64 - py34-none-linux_x86_64 - py33-none-linux_x86_64 - py32-none-linux_x86_64 - py31-none-linux_x86_64 - py30-none-linux_x86_64 - cp39-none-any - py39-none-any - py3-none-any - py38-none-any - py37-none-any - py36-none-any - py35-none-any - py34-none-any - py33-none-any - py32-none-any - py31-none-any - py30-none-any - "###); - } - - /// Check full tag ordering. - /// The list is displayed in decreasing priority. - /// - /// A reference list can be generated with: - /// ```text - /// $ python -c "from packaging import tags; [print(tag) for tag in tags.sys_tags()]"` - /// ``` - #[test] - fn test_system_tags_manylinux() { - let tags = Tags::from_env( - &Platform::new( - Os::Manylinux { - major: 2, - minor: 28, - }, - Arch::X86_64, - ), - (3, 9), - "cpython", - (3, 9), - true, - false, - ) - .unwrap(); - assert_snapshot!( - tags, - @r###" - cp39-cp39-manylinux_2_28_x86_64 - cp39-cp39-manylinux_2_27_x86_64 - cp39-cp39-manylinux_2_26_x86_64 - cp39-cp39-manylinux_2_25_x86_64 - cp39-cp39-manylinux_2_24_x86_64 - cp39-cp39-manylinux_2_23_x86_64 - cp39-cp39-manylinux_2_22_x86_64 - cp39-cp39-manylinux_2_21_x86_64 - cp39-cp39-manylinux_2_20_x86_64 - cp39-cp39-manylinux_2_19_x86_64 - cp39-cp39-manylinux_2_18_x86_64 - cp39-cp39-manylinux_2_17_x86_64 - cp39-cp39-manylinux2014_x86_64 - cp39-cp39-manylinux_2_16_x86_64 - cp39-cp39-manylinux_2_15_x86_64 - cp39-cp39-manylinux_2_14_x86_64 - cp39-cp39-manylinux_2_13_x86_64 - cp39-cp39-manylinux_2_12_x86_64 - cp39-cp39-manylinux2010_x86_64 - cp39-cp39-manylinux_2_11_x86_64 - cp39-cp39-manylinux_2_10_x86_64 - cp39-cp39-manylinux_2_9_x86_64 - cp39-cp39-manylinux_2_8_x86_64 - cp39-cp39-manylinux_2_7_x86_64 - cp39-cp39-manylinux_2_6_x86_64 - cp39-cp39-manylinux_2_5_x86_64 - cp39-cp39-manylinux1_x86_64 - cp39-cp39-linux_x86_64 - cp39-abi3-manylinux_2_28_x86_64 - cp39-abi3-manylinux_2_27_x86_64 - cp39-abi3-manylinux_2_26_x86_64 - cp39-abi3-manylinux_2_25_x86_64 - cp39-abi3-manylinux_2_24_x86_64 - cp39-abi3-manylinux_2_23_x86_64 - cp39-abi3-manylinux_2_22_x86_64 - cp39-abi3-manylinux_2_21_x86_64 - cp39-abi3-manylinux_2_20_x86_64 - cp39-abi3-manylinux_2_19_x86_64 - cp39-abi3-manylinux_2_18_x86_64 - cp39-abi3-manylinux_2_17_x86_64 - cp39-abi3-manylinux2014_x86_64 - cp39-abi3-manylinux_2_16_x86_64 - cp39-abi3-manylinux_2_15_x86_64 - cp39-abi3-manylinux_2_14_x86_64 - cp39-abi3-manylinux_2_13_x86_64 - cp39-abi3-manylinux_2_12_x86_64 - cp39-abi3-manylinux2010_x86_64 - cp39-abi3-manylinux_2_11_x86_64 - cp39-abi3-manylinux_2_10_x86_64 - cp39-abi3-manylinux_2_9_x86_64 - cp39-abi3-manylinux_2_8_x86_64 - cp39-abi3-manylinux_2_7_x86_64 - cp39-abi3-manylinux_2_6_x86_64 - cp39-abi3-manylinux_2_5_x86_64 - cp39-abi3-manylinux1_x86_64 - cp39-abi3-linux_x86_64 - cp39-none-manylinux_2_28_x86_64 - cp39-none-manylinux_2_27_x86_64 - cp39-none-manylinux_2_26_x86_64 - cp39-none-manylinux_2_25_x86_64 - cp39-none-manylinux_2_24_x86_64 - cp39-none-manylinux_2_23_x86_64 - cp39-none-manylinux_2_22_x86_64 - cp39-none-manylinux_2_21_x86_64 - cp39-none-manylinux_2_20_x86_64 - cp39-none-manylinux_2_19_x86_64 - cp39-none-manylinux_2_18_x86_64 - cp39-none-manylinux_2_17_x86_64 - cp39-none-manylinux2014_x86_64 - cp39-none-manylinux_2_16_x86_64 - cp39-none-manylinux_2_15_x86_64 - cp39-none-manylinux_2_14_x86_64 - cp39-none-manylinux_2_13_x86_64 - cp39-none-manylinux_2_12_x86_64 - cp39-none-manylinux2010_x86_64 - cp39-none-manylinux_2_11_x86_64 - cp39-none-manylinux_2_10_x86_64 - cp39-none-manylinux_2_9_x86_64 - cp39-none-manylinux_2_8_x86_64 - cp39-none-manylinux_2_7_x86_64 - cp39-none-manylinux_2_6_x86_64 - cp39-none-manylinux_2_5_x86_64 - cp39-none-manylinux1_x86_64 - cp39-none-linux_x86_64 - cp38-abi3-manylinux_2_28_x86_64 - cp38-abi3-manylinux_2_27_x86_64 - cp38-abi3-manylinux_2_26_x86_64 - cp38-abi3-manylinux_2_25_x86_64 - cp38-abi3-manylinux_2_24_x86_64 - cp38-abi3-manylinux_2_23_x86_64 - cp38-abi3-manylinux_2_22_x86_64 - cp38-abi3-manylinux_2_21_x86_64 - cp38-abi3-manylinux_2_20_x86_64 - cp38-abi3-manylinux_2_19_x86_64 - cp38-abi3-manylinux_2_18_x86_64 - cp38-abi3-manylinux_2_17_x86_64 - cp38-abi3-manylinux2014_x86_64 - cp38-abi3-manylinux_2_16_x86_64 - cp38-abi3-manylinux_2_15_x86_64 - cp38-abi3-manylinux_2_14_x86_64 - cp38-abi3-manylinux_2_13_x86_64 - cp38-abi3-manylinux_2_12_x86_64 - cp38-abi3-manylinux2010_x86_64 - cp38-abi3-manylinux_2_11_x86_64 - cp38-abi3-manylinux_2_10_x86_64 - cp38-abi3-manylinux_2_9_x86_64 - cp38-abi3-manylinux_2_8_x86_64 - cp38-abi3-manylinux_2_7_x86_64 - cp38-abi3-manylinux_2_6_x86_64 - cp38-abi3-manylinux_2_5_x86_64 - cp38-abi3-manylinux1_x86_64 - cp38-abi3-linux_x86_64 - cp37-abi3-manylinux_2_28_x86_64 - cp37-abi3-manylinux_2_27_x86_64 - cp37-abi3-manylinux_2_26_x86_64 - cp37-abi3-manylinux_2_25_x86_64 - cp37-abi3-manylinux_2_24_x86_64 - cp37-abi3-manylinux_2_23_x86_64 - cp37-abi3-manylinux_2_22_x86_64 - cp37-abi3-manylinux_2_21_x86_64 - cp37-abi3-manylinux_2_20_x86_64 - cp37-abi3-manylinux_2_19_x86_64 - cp37-abi3-manylinux_2_18_x86_64 - cp37-abi3-manylinux_2_17_x86_64 - cp37-abi3-manylinux2014_x86_64 - cp37-abi3-manylinux_2_16_x86_64 - cp37-abi3-manylinux_2_15_x86_64 - cp37-abi3-manylinux_2_14_x86_64 - cp37-abi3-manylinux_2_13_x86_64 - cp37-abi3-manylinux_2_12_x86_64 - cp37-abi3-manylinux2010_x86_64 - cp37-abi3-manylinux_2_11_x86_64 - cp37-abi3-manylinux_2_10_x86_64 - cp37-abi3-manylinux_2_9_x86_64 - cp37-abi3-manylinux_2_8_x86_64 - cp37-abi3-manylinux_2_7_x86_64 - cp37-abi3-manylinux_2_6_x86_64 - cp37-abi3-manylinux_2_5_x86_64 - cp37-abi3-manylinux1_x86_64 - cp37-abi3-linux_x86_64 - cp36-abi3-manylinux_2_28_x86_64 - cp36-abi3-manylinux_2_27_x86_64 - cp36-abi3-manylinux_2_26_x86_64 - cp36-abi3-manylinux_2_25_x86_64 - cp36-abi3-manylinux_2_24_x86_64 - cp36-abi3-manylinux_2_23_x86_64 - cp36-abi3-manylinux_2_22_x86_64 - cp36-abi3-manylinux_2_21_x86_64 - cp36-abi3-manylinux_2_20_x86_64 - cp36-abi3-manylinux_2_19_x86_64 - cp36-abi3-manylinux_2_18_x86_64 - cp36-abi3-manylinux_2_17_x86_64 - cp36-abi3-manylinux2014_x86_64 - cp36-abi3-manylinux_2_16_x86_64 - cp36-abi3-manylinux_2_15_x86_64 - cp36-abi3-manylinux_2_14_x86_64 - cp36-abi3-manylinux_2_13_x86_64 - cp36-abi3-manylinux_2_12_x86_64 - cp36-abi3-manylinux2010_x86_64 - cp36-abi3-manylinux_2_11_x86_64 - cp36-abi3-manylinux_2_10_x86_64 - cp36-abi3-manylinux_2_9_x86_64 - cp36-abi3-manylinux_2_8_x86_64 - cp36-abi3-manylinux_2_7_x86_64 - cp36-abi3-manylinux_2_6_x86_64 - cp36-abi3-manylinux_2_5_x86_64 - cp36-abi3-manylinux1_x86_64 - cp36-abi3-linux_x86_64 - cp35-abi3-manylinux_2_28_x86_64 - cp35-abi3-manylinux_2_27_x86_64 - cp35-abi3-manylinux_2_26_x86_64 - cp35-abi3-manylinux_2_25_x86_64 - cp35-abi3-manylinux_2_24_x86_64 - cp35-abi3-manylinux_2_23_x86_64 - cp35-abi3-manylinux_2_22_x86_64 - cp35-abi3-manylinux_2_21_x86_64 - cp35-abi3-manylinux_2_20_x86_64 - cp35-abi3-manylinux_2_19_x86_64 - cp35-abi3-manylinux_2_18_x86_64 - cp35-abi3-manylinux_2_17_x86_64 - cp35-abi3-manylinux2014_x86_64 - cp35-abi3-manylinux_2_16_x86_64 - cp35-abi3-manylinux_2_15_x86_64 - cp35-abi3-manylinux_2_14_x86_64 - cp35-abi3-manylinux_2_13_x86_64 - cp35-abi3-manylinux_2_12_x86_64 - cp35-abi3-manylinux2010_x86_64 - cp35-abi3-manylinux_2_11_x86_64 - cp35-abi3-manylinux_2_10_x86_64 - cp35-abi3-manylinux_2_9_x86_64 - cp35-abi3-manylinux_2_8_x86_64 - cp35-abi3-manylinux_2_7_x86_64 - cp35-abi3-manylinux_2_6_x86_64 - cp35-abi3-manylinux_2_5_x86_64 - cp35-abi3-manylinux1_x86_64 - cp35-abi3-linux_x86_64 - cp34-abi3-manylinux_2_28_x86_64 - cp34-abi3-manylinux_2_27_x86_64 - cp34-abi3-manylinux_2_26_x86_64 - cp34-abi3-manylinux_2_25_x86_64 - cp34-abi3-manylinux_2_24_x86_64 - cp34-abi3-manylinux_2_23_x86_64 - cp34-abi3-manylinux_2_22_x86_64 - cp34-abi3-manylinux_2_21_x86_64 - cp34-abi3-manylinux_2_20_x86_64 - cp34-abi3-manylinux_2_19_x86_64 - cp34-abi3-manylinux_2_18_x86_64 - cp34-abi3-manylinux_2_17_x86_64 - cp34-abi3-manylinux2014_x86_64 - cp34-abi3-manylinux_2_16_x86_64 - cp34-abi3-manylinux_2_15_x86_64 - cp34-abi3-manylinux_2_14_x86_64 - cp34-abi3-manylinux_2_13_x86_64 - cp34-abi3-manylinux_2_12_x86_64 - cp34-abi3-manylinux2010_x86_64 - cp34-abi3-manylinux_2_11_x86_64 - cp34-abi3-manylinux_2_10_x86_64 - cp34-abi3-manylinux_2_9_x86_64 - cp34-abi3-manylinux_2_8_x86_64 - cp34-abi3-manylinux_2_7_x86_64 - cp34-abi3-manylinux_2_6_x86_64 - cp34-abi3-manylinux_2_5_x86_64 - cp34-abi3-manylinux1_x86_64 - cp34-abi3-linux_x86_64 - cp33-abi3-manylinux_2_28_x86_64 - cp33-abi3-manylinux_2_27_x86_64 - cp33-abi3-manylinux_2_26_x86_64 - cp33-abi3-manylinux_2_25_x86_64 - cp33-abi3-manylinux_2_24_x86_64 - cp33-abi3-manylinux_2_23_x86_64 - cp33-abi3-manylinux_2_22_x86_64 - cp33-abi3-manylinux_2_21_x86_64 - cp33-abi3-manylinux_2_20_x86_64 - cp33-abi3-manylinux_2_19_x86_64 - cp33-abi3-manylinux_2_18_x86_64 - cp33-abi3-manylinux_2_17_x86_64 - cp33-abi3-manylinux2014_x86_64 - cp33-abi3-manylinux_2_16_x86_64 - cp33-abi3-manylinux_2_15_x86_64 - cp33-abi3-manylinux_2_14_x86_64 - cp33-abi3-manylinux_2_13_x86_64 - cp33-abi3-manylinux_2_12_x86_64 - cp33-abi3-manylinux2010_x86_64 - cp33-abi3-manylinux_2_11_x86_64 - cp33-abi3-manylinux_2_10_x86_64 - cp33-abi3-manylinux_2_9_x86_64 - cp33-abi3-manylinux_2_8_x86_64 - cp33-abi3-manylinux_2_7_x86_64 - cp33-abi3-manylinux_2_6_x86_64 - cp33-abi3-manylinux_2_5_x86_64 - cp33-abi3-manylinux1_x86_64 - cp33-abi3-linux_x86_64 - cp32-abi3-manylinux_2_28_x86_64 - cp32-abi3-manylinux_2_27_x86_64 - cp32-abi3-manylinux_2_26_x86_64 - cp32-abi3-manylinux_2_25_x86_64 - cp32-abi3-manylinux_2_24_x86_64 - cp32-abi3-manylinux_2_23_x86_64 - cp32-abi3-manylinux_2_22_x86_64 - cp32-abi3-manylinux_2_21_x86_64 - cp32-abi3-manylinux_2_20_x86_64 - cp32-abi3-manylinux_2_19_x86_64 - cp32-abi3-manylinux_2_18_x86_64 - cp32-abi3-manylinux_2_17_x86_64 - cp32-abi3-manylinux2014_x86_64 - cp32-abi3-manylinux_2_16_x86_64 - cp32-abi3-manylinux_2_15_x86_64 - cp32-abi3-manylinux_2_14_x86_64 - cp32-abi3-manylinux_2_13_x86_64 - cp32-abi3-manylinux_2_12_x86_64 - cp32-abi3-manylinux2010_x86_64 - cp32-abi3-manylinux_2_11_x86_64 - cp32-abi3-manylinux_2_10_x86_64 - cp32-abi3-manylinux_2_9_x86_64 - cp32-abi3-manylinux_2_8_x86_64 - cp32-abi3-manylinux_2_7_x86_64 - cp32-abi3-manylinux_2_6_x86_64 - cp32-abi3-manylinux_2_5_x86_64 - cp32-abi3-manylinux1_x86_64 - cp32-abi3-linux_x86_64 - py39-none-manylinux_2_28_x86_64 - py39-none-manylinux_2_27_x86_64 - py39-none-manylinux_2_26_x86_64 - py39-none-manylinux_2_25_x86_64 - py39-none-manylinux_2_24_x86_64 - py39-none-manylinux_2_23_x86_64 - py39-none-manylinux_2_22_x86_64 - py39-none-manylinux_2_21_x86_64 - py39-none-manylinux_2_20_x86_64 - py39-none-manylinux_2_19_x86_64 - py39-none-manylinux_2_18_x86_64 - py39-none-manylinux_2_17_x86_64 - py39-none-manylinux2014_x86_64 - py39-none-manylinux_2_16_x86_64 - py39-none-manylinux_2_15_x86_64 - py39-none-manylinux_2_14_x86_64 - py39-none-manylinux_2_13_x86_64 - py39-none-manylinux_2_12_x86_64 - py39-none-manylinux2010_x86_64 - py39-none-manylinux_2_11_x86_64 - py39-none-manylinux_2_10_x86_64 - py39-none-manylinux_2_9_x86_64 - py39-none-manylinux_2_8_x86_64 - py39-none-manylinux_2_7_x86_64 - py39-none-manylinux_2_6_x86_64 - py39-none-manylinux_2_5_x86_64 - py39-none-manylinux1_x86_64 - py39-none-linux_x86_64 - py3-none-manylinux_2_28_x86_64 - py3-none-manylinux_2_27_x86_64 - py3-none-manylinux_2_26_x86_64 - py3-none-manylinux_2_25_x86_64 - py3-none-manylinux_2_24_x86_64 - py3-none-manylinux_2_23_x86_64 - py3-none-manylinux_2_22_x86_64 - py3-none-manylinux_2_21_x86_64 - py3-none-manylinux_2_20_x86_64 - py3-none-manylinux_2_19_x86_64 - py3-none-manylinux_2_18_x86_64 - py3-none-manylinux_2_17_x86_64 - py3-none-manylinux2014_x86_64 - py3-none-manylinux_2_16_x86_64 - py3-none-manylinux_2_15_x86_64 - py3-none-manylinux_2_14_x86_64 - py3-none-manylinux_2_13_x86_64 - py3-none-manylinux_2_12_x86_64 - py3-none-manylinux2010_x86_64 - py3-none-manylinux_2_11_x86_64 - py3-none-manylinux_2_10_x86_64 - py3-none-manylinux_2_9_x86_64 - py3-none-manylinux_2_8_x86_64 - py3-none-manylinux_2_7_x86_64 - py3-none-manylinux_2_6_x86_64 - py3-none-manylinux_2_5_x86_64 - py3-none-manylinux1_x86_64 - py3-none-linux_x86_64 - py38-none-manylinux_2_28_x86_64 - py38-none-manylinux_2_27_x86_64 - py38-none-manylinux_2_26_x86_64 - py38-none-manylinux_2_25_x86_64 - py38-none-manylinux_2_24_x86_64 - py38-none-manylinux_2_23_x86_64 - py38-none-manylinux_2_22_x86_64 - py38-none-manylinux_2_21_x86_64 - py38-none-manylinux_2_20_x86_64 - py38-none-manylinux_2_19_x86_64 - py38-none-manylinux_2_18_x86_64 - py38-none-manylinux_2_17_x86_64 - py38-none-manylinux2014_x86_64 - py38-none-manylinux_2_16_x86_64 - py38-none-manylinux_2_15_x86_64 - py38-none-manylinux_2_14_x86_64 - py38-none-manylinux_2_13_x86_64 - py38-none-manylinux_2_12_x86_64 - py38-none-manylinux2010_x86_64 - py38-none-manylinux_2_11_x86_64 - py38-none-manylinux_2_10_x86_64 - py38-none-manylinux_2_9_x86_64 - py38-none-manylinux_2_8_x86_64 - py38-none-manylinux_2_7_x86_64 - py38-none-manylinux_2_6_x86_64 - py38-none-manylinux_2_5_x86_64 - py38-none-manylinux1_x86_64 - py38-none-linux_x86_64 - py37-none-manylinux_2_28_x86_64 - py37-none-manylinux_2_27_x86_64 - py37-none-manylinux_2_26_x86_64 - py37-none-manylinux_2_25_x86_64 - py37-none-manylinux_2_24_x86_64 - py37-none-manylinux_2_23_x86_64 - py37-none-manylinux_2_22_x86_64 - py37-none-manylinux_2_21_x86_64 - py37-none-manylinux_2_20_x86_64 - py37-none-manylinux_2_19_x86_64 - py37-none-manylinux_2_18_x86_64 - py37-none-manylinux_2_17_x86_64 - py37-none-manylinux2014_x86_64 - py37-none-manylinux_2_16_x86_64 - py37-none-manylinux_2_15_x86_64 - py37-none-manylinux_2_14_x86_64 - py37-none-manylinux_2_13_x86_64 - py37-none-manylinux_2_12_x86_64 - py37-none-manylinux2010_x86_64 - py37-none-manylinux_2_11_x86_64 - py37-none-manylinux_2_10_x86_64 - py37-none-manylinux_2_9_x86_64 - py37-none-manylinux_2_8_x86_64 - py37-none-manylinux_2_7_x86_64 - py37-none-manylinux_2_6_x86_64 - py37-none-manylinux_2_5_x86_64 - py37-none-manylinux1_x86_64 - py37-none-linux_x86_64 - py36-none-manylinux_2_28_x86_64 - py36-none-manylinux_2_27_x86_64 - py36-none-manylinux_2_26_x86_64 - py36-none-manylinux_2_25_x86_64 - py36-none-manylinux_2_24_x86_64 - py36-none-manylinux_2_23_x86_64 - py36-none-manylinux_2_22_x86_64 - py36-none-manylinux_2_21_x86_64 - py36-none-manylinux_2_20_x86_64 - py36-none-manylinux_2_19_x86_64 - py36-none-manylinux_2_18_x86_64 - py36-none-manylinux_2_17_x86_64 - py36-none-manylinux2014_x86_64 - py36-none-manylinux_2_16_x86_64 - py36-none-manylinux_2_15_x86_64 - py36-none-manylinux_2_14_x86_64 - py36-none-manylinux_2_13_x86_64 - py36-none-manylinux_2_12_x86_64 - py36-none-manylinux2010_x86_64 - py36-none-manylinux_2_11_x86_64 - py36-none-manylinux_2_10_x86_64 - py36-none-manylinux_2_9_x86_64 - py36-none-manylinux_2_8_x86_64 - py36-none-manylinux_2_7_x86_64 - py36-none-manylinux_2_6_x86_64 - py36-none-manylinux_2_5_x86_64 - py36-none-manylinux1_x86_64 - py36-none-linux_x86_64 - py35-none-manylinux_2_28_x86_64 - py35-none-manylinux_2_27_x86_64 - py35-none-manylinux_2_26_x86_64 - py35-none-manylinux_2_25_x86_64 - py35-none-manylinux_2_24_x86_64 - py35-none-manylinux_2_23_x86_64 - py35-none-manylinux_2_22_x86_64 - py35-none-manylinux_2_21_x86_64 - py35-none-manylinux_2_20_x86_64 - py35-none-manylinux_2_19_x86_64 - py35-none-manylinux_2_18_x86_64 - py35-none-manylinux_2_17_x86_64 - py35-none-manylinux2014_x86_64 - py35-none-manylinux_2_16_x86_64 - py35-none-manylinux_2_15_x86_64 - py35-none-manylinux_2_14_x86_64 - py35-none-manylinux_2_13_x86_64 - py35-none-manylinux_2_12_x86_64 - py35-none-manylinux2010_x86_64 - py35-none-manylinux_2_11_x86_64 - py35-none-manylinux_2_10_x86_64 - py35-none-manylinux_2_9_x86_64 - py35-none-manylinux_2_8_x86_64 - py35-none-manylinux_2_7_x86_64 - py35-none-manylinux_2_6_x86_64 - py35-none-manylinux_2_5_x86_64 - py35-none-manylinux1_x86_64 - py35-none-linux_x86_64 - py34-none-manylinux_2_28_x86_64 - py34-none-manylinux_2_27_x86_64 - py34-none-manylinux_2_26_x86_64 - py34-none-manylinux_2_25_x86_64 - py34-none-manylinux_2_24_x86_64 - py34-none-manylinux_2_23_x86_64 - py34-none-manylinux_2_22_x86_64 - py34-none-manylinux_2_21_x86_64 - py34-none-manylinux_2_20_x86_64 - py34-none-manylinux_2_19_x86_64 - py34-none-manylinux_2_18_x86_64 - py34-none-manylinux_2_17_x86_64 - py34-none-manylinux2014_x86_64 - py34-none-manylinux_2_16_x86_64 - py34-none-manylinux_2_15_x86_64 - py34-none-manylinux_2_14_x86_64 - py34-none-manylinux_2_13_x86_64 - py34-none-manylinux_2_12_x86_64 - py34-none-manylinux2010_x86_64 - py34-none-manylinux_2_11_x86_64 - py34-none-manylinux_2_10_x86_64 - py34-none-manylinux_2_9_x86_64 - py34-none-manylinux_2_8_x86_64 - py34-none-manylinux_2_7_x86_64 - py34-none-manylinux_2_6_x86_64 - py34-none-manylinux_2_5_x86_64 - py34-none-manylinux1_x86_64 - py34-none-linux_x86_64 - py33-none-manylinux_2_28_x86_64 - py33-none-manylinux_2_27_x86_64 - py33-none-manylinux_2_26_x86_64 - py33-none-manylinux_2_25_x86_64 - py33-none-manylinux_2_24_x86_64 - py33-none-manylinux_2_23_x86_64 - py33-none-manylinux_2_22_x86_64 - py33-none-manylinux_2_21_x86_64 - py33-none-manylinux_2_20_x86_64 - py33-none-manylinux_2_19_x86_64 - py33-none-manylinux_2_18_x86_64 - py33-none-manylinux_2_17_x86_64 - py33-none-manylinux2014_x86_64 - py33-none-manylinux_2_16_x86_64 - py33-none-manylinux_2_15_x86_64 - py33-none-manylinux_2_14_x86_64 - py33-none-manylinux_2_13_x86_64 - py33-none-manylinux_2_12_x86_64 - py33-none-manylinux2010_x86_64 - py33-none-manylinux_2_11_x86_64 - py33-none-manylinux_2_10_x86_64 - py33-none-manylinux_2_9_x86_64 - py33-none-manylinux_2_8_x86_64 - py33-none-manylinux_2_7_x86_64 - py33-none-manylinux_2_6_x86_64 - py33-none-manylinux_2_5_x86_64 - py33-none-manylinux1_x86_64 - py33-none-linux_x86_64 - py32-none-manylinux_2_28_x86_64 - py32-none-manylinux_2_27_x86_64 - py32-none-manylinux_2_26_x86_64 - py32-none-manylinux_2_25_x86_64 - py32-none-manylinux_2_24_x86_64 - py32-none-manylinux_2_23_x86_64 - py32-none-manylinux_2_22_x86_64 - py32-none-manylinux_2_21_x86_64 - py32-none-manylinux_2_20_x86_64 - py32-none-manylinux_2_19_x86_64 - py32-none-manylinux_2_18_x86_64 - py32-none-manylinux_2_17_x86_64 - py32-none-manylinux2014_x86_64 - py32-none-manylinux_2_16_x86_64 - py32-none-manylinux_2_15_x86_64 - py32-none-manylinux_2_14_x86_64 - py32-none-manylinux_2_13_x86_64 - py32-none-manylinux_2_12_x86_64 - py32-none-manylinux2010_x86_64 - py32-none-manylinux_2_11_x86_64 - py32-none-manylinux_2_10_x86_64 - py32-none-manylinux_2_9_x86_64 - py32-none-manylinux_2_8_x86_64 - py32-none-manylinux_2_7_x86_64 - py32-none-manylinux_2_6_x86_64 - py32-none-manylinux_2_5_x86_64 - py32-none-manylinux1_x86_64 - py32-none-linux_x86_64 - py31-none-manylinux_2_28_x86_64 - py31-none-manylinux_2_27_x86_64 - py31-none-manylinux_2_26_x86_64 - py31-none-manylinux_2_25_x86_64 - py31-none-manylinux_2_24_x86_64 - py31-none-manylinux_2_23_x86_64 - py31-none-manylinux_2_22_x86_64 - py31-none-manylinux_2_21_x86_64 - py31-none-manylinux_2_20_x86_64 - py31-none-manylinux_2_19_x86_64 - py31-none-manylinux_2_18_x86_64 - py31-none-manylinux_2_17_x86_64 - py31-none-manylinux2014_x86_64 - py31-none-manylinux_2_16_x86_64 - py31-none-manylinux_2_15_x86_64 - py31-none-manylinux_2_14_x86_64 - py31-none-manylinux_2_13_x86_64 - py31-none-manylinux_2_12_x86_64 - py31-none-manylinux2010_x86_64 - py31-none-manylinux_2_11_x86_64 - py31-none-manylinux_2_10_x86_64 - py31-none-manylinux_2_9_x86_64 - py31-none-manylinux_2_8_x86_64 - py31-none-manylinux_2_7_x86_64 - py31-none-manylinux_2_6_x86_64 - py31-none-manylinux_2_5_x86_64 - py31-none-manylinux1_x86_64 - py31-none-linux_x86_64 - py30-none-manylinux_2_28_x86_64 - py30-none-manylinux_2_27_x86_64 - py30-none-manylinux_2_26_x86_64 - py30-none-manylinux_2_25_x86_64 - py30-none-manylinux_2_24_x86_64 - py30-none-manylinux_2_23_x86_64 - py30-none-manylinux_2_22_x86_64 - py30-none-manylinux_2_21_x86_64 - py30-none-manylinux_2_20_x86_64 - py30-none-manylinux_2_19_x86_64 - py30-none-manylinux_2_18_x86_64 - py30-none-manylinux_2_17_x86_64 - py30-none-manylinux2014_x86_64 - py30-none-manylinux_2_16_x86_64 - py30-none-manylinux_2_15_x86_64 - py30-none-manylinux_2_14_x86_64 - py30-none-manylinux_2_13_x86_64 - py30-none-manylinux_2_12_x86_64 - py30-none-manylinux2010_x86_64 - py30-none-manylinux_2_11_x86_64 - py30-none-manylinux_2_10_x86_64 - py30-none-manylinux_2_9_x86_64 - py30-none-manylinux_2_8_x86_64 - py30-none-manylinux_2_7_x86_64 - py30-none-manylinux_2_6_x86_64 - py30-none-manylinux_2_5_x86_64 - py30-none-manylinux1_x86_64 - py30-none-linux_x86_64 - cp39-none-any - py39-none-any - py3-none-any - py38-none-any - py37-none-any - py36-none-any - py35-none-any - py34-none-any - py33-none-any - py32-none-any - py31-none-any - py30-none-any - "### - ); - } - - #[test] - fn test_system_tags_macos() { - let tags = Tags::from_env( - &Platform::new( - Os::Macos { - major: 14, - minor: 0, - }, - Arch::Aarch64, - ), - (3, 9), - "cpython", - (3, 9), - false, - false, - ) - .unwrap(); - assert_snapshot!( - tags, - @r###" - cp39-cp39-macosx_14_0_arm64 - cp39-cp39-macosx_14_0_universal2 - cp39-cp39-macosx_13_0_arm64 - cp39-cp39-macosx_13_0_universal2 - cp39-cp39-macosx_12_0_arm64 - cp39-cp39-macosx_12_0_universal2 - cp39-cp39-macosx_11_0_arm64 - cp39-cp39-macosx_11_0_universal2 - cp39-cp39-macosx_10_16_universal2 - cp39-cp39-macosx_10_15_universal2 - cp39-cp39-macosx_10_14_universal2 - cp39-cp39-macosx_10_13_universal2 - cp39-cp39-macosx_10_12_universal2 - cp39-cp39-macosx_10_11_universal2 - cp39-cp39-macosx_10_10_universal2 - cp39-cp39-macosx_10_9_universal2 - cp39-cp39-macosx_10_8_universal2 - cp39-cp39-macosx_10_7_universal2 - cp39-cp39-macosx_10_6_universal2 - cp39-cp39-macosx_10_5_universal2 - cp39-cp39-macosx_10_4_universal2 - cp39-abi3-macosx_14_0_arm64 - cp39-abi3-macosx_14_0_universal2 - cp39-abi3-macosx_13_0_arm64 - cp39-abi3-macosx_13_0_universal2 - cp39-abi3-macosx_12_0_arm64 - cp39-abi3-macosx_12_0_universal2 - cp39-abi3-macosx_11_0_arm64 - cp39-abi3-macosx_11_0_universal2 - cp39-abi3-macosx_10_16_universal2 - cp39-abi3-macosx_10_15_universal2 - cp39-abi3-macosx_10_14_universal2 - cp39-abi3-macosx_10_13_universal2 - cp39-abi3-macosx_10_12_universal2 - cp39-abi3-macosx_10_11_universal2 - cp39-abi3-macosx_10_10_universal2 - cp39-abi3-macosx_10_9_universal2 - cp39-abi3-macosx_10_8_universal2 - cp39-abi3-macosx_10_7_universal2 - cp39-abi3-macosx_10_6_universal2 - cp39-abi3-macosx_10_5_universal2 - cp39-abi3-macosx_10_4_universal2 - cp39-none-macosx_14_0_arm64 - cp39-none-macosx_14_0_universal2 - cp39-none-macosx_13_0_arm64 - cp39-none-macosx_13_0_universal2 - cp39-none-macosx_12_0_arm64 - cp39-none-macosx_12_0_universal2 - cp39-none-macosx_11_0_arm64 - cp39-none-macosx_11_0_universal2 - cp39-none-macosx_10_16_universal2 - cp39-none-macosx_10_15_universal2 - cp39-none-macosx_10_14_universal2 - cp39-none-macosx_10_13_universal2 - cp39-none-macosx_10_12_universal2 - cp39-none-macosx_10_11_universal2 - cp39-none-macosx_10_10_universal2 - cp39-none-macosx_10_9_universal2 - cp39-none-macosx_10_8_universal2 - cp39-none-macosx_10_7_universal2 - cp39-none-macosx_10_6_universal2 - cp39-none-macosx_10_5_universal2 - cp39-none-macosx_10_4_universal2 - cp38-abi3-macosx_14_0_arm64 - cp38-abi3-macosx_14_0_universal2 - cp38-abi3-macosx_13_0_arm64 - cp38-abi3-macosx_13_0_universal2 - cp38-abi3-macosx_12_0_arm64 - cp38-abi3-macosx_12_0_universal2 - cp38-abi3-macosx_11_0_arm64 - cp38-abi3-macosx_11_0_universal2 - cp38-abi3-macosx_10_16_universal2 - cp38-abi3-macosx_10_15_universal2 - cp38-abi3-macosx_10_14_universal2 - cp38-abi3-macosx_10_13_universal2 - cp38-abi3-macosx_10_12_universal2 - cp38-abi3-macosx_10_11_universal2 - cp38-abi3-macosx_10_10_universal2 - cp38-abi3-macosx_10_9_universal2 - cp38-abi3-macosx_10_8_universal2 - cp38-abi3-macosx_10_7_universal2 - cp38-abi3-macosx_10_6_universal2 - cp38-abi3-macosx_10_5_universal2 - cp38-abi3-macosx_10_4_universal2 - cp37-abi3-macosx_14_0_arm64 - cp37-abi3-macosx_14_0_universal2 - cp37-abi3-macosx_13_0_arm64 - cp37-abi3-macosx_13_0_universal2 - cp37-abi3-macosx_12_0_arm64 - cp37-abi3-macosx_12_0_universal2 - cp37-abi3-macosx_11_0_arm64 - cp37-abi3-macosx_11_0_universal2 - cp37-abi3-macosx_10_16_universal2 - cp37-abi3-macosx_10_15_universal2 - cp37-abi3-macosx_10_14_universal2 - cp37-abi3-macosx_10_13_universal2 - cp37-abi3-macosx_10_12_universal2 - cp37-abi3-macosx_10_11_universal2 - cp37-abi3-macosx_10_10_universal2 - cp37-abi3-macosx_10_9_universal2 - cp37-abi3-macosx_10_8_universal2 - cp37-abi3-macosx_10_7_universal2 - cp37-abi3-macosx_10_6_universal2 - cp37-abi3-macosx_10_5_universal2 - cp37-abi3-macosx_10_4_universal2 - cp36-abi3-macosx_14_0_arm64 - cp36-abi3-macosx_14_0_universal2 - cp36-abi3-macosx_13_0_arm64 - cp36-abi3-macosx_13_0_universal2 - cp36-abi3-macosx_12_0_arm64 - cp36-abi3-macosx_12_0_universal2 - cp36-abi3-macosx_11_0_arm64 - cp36-abi3-macosx_11_0_universal2 - cp36-abi3-macosx_10_16_universal2 - cp36-abi3-macosx_10_15_universal2 - cp36-abi3-macosx_10_14_universal2 - cp36-abi3-macosx_10_13_universal2 - cp36-abi3-macosx_10_12_universal2 - cp36-abi3-macosx_10_11_universal2 - cp36-abi3-macosx_10_10_universal2 - cp36-abi3-macosx_10_9_universal2 - cp36-abi3-macosx_10_8_universal2 - cp36-abi3-macosx_10_7_universal2 - cp36-abi3-macosx_10_6_universal2 - cp36-abi3-macosx_10_5_universal2 - cp36-abi3-macosx_10_4_universal2 - cp35-abi3-macosx_14_0_arm64 - cp35-abi3-macosx_14_0_universal2 - cp35-abi3-macosx_13_0_arm64 - cp35-abi3-macosx_13_0_universal2 - cp35-abi3-macosx_12_0_arm64 - cp35-abi3-macosx_12_0_universal2 - cp35-abi3-macosx_11_0_arm64 - cp35-abi3-macosx_11_0_universal2 - cp35-abi3-macosx_10_16_universal2 - cp35-abi3-macosx_10_15_universal2 - cp35-abi3-macosx_10_14_universal2 - cp35-abi3-macosx_10_13_universal2 - cp35-abi3-macosx_10_12_universal2 - cp35-abi3-macosx_10_11_universal2 - cp35-abi3-macosx_10_10_universal2 - cp35-abi3-macosx_10_9_universal2 - cp35-abi3-macosx_10_8_universal2 - cp35-abi3-macosx_10_7_universal2 - cp35-abi3-macosx_10_6_universal2 - cp35-abi3-macosx_10_5_universal2 - cp35-abi3-macosx_10_4_universal2 - cp34-abi3-macosx_14_0_arm64 - cp34-abi3-macosx_14_0_universal2 - cp34-abi3-macosx_13_0_arm64 - cp34-abi3-macosx_13_0_universal2 - cp34-abi3-macosx_12_0_arm64 - cp34-abi3-macosx_12_0_universal2 - cp34-abi3-macosx_11_0_arm64 - cp34-abi3-macosx_11_0_universal2 - cp34-abi3-macosx_10_16_universal2 - cp34-abi3-macosx_10_15_universal2 - cp34-abi3-macosx_10_14_universal2 - cp34-abi3-macosx_10_13_universal2 - cp34-abi3-macosx_10_12_universal2 - cp34-abi3-macosx_10_11_universal2 - cp34-abi3-macosx_10_10_universal2 - cp34-abi3-macosx_10_9_universal2 - cp34-abi3-macosx_10_8_universal2 - cp34-abi3-macosx_10_7_universal2 - cp34-abi3-macosx_10_6_universal2 - cp34-abi3-macosx_10_5_universal2 - cp34-abi3-macosx_10_4_universal2 - cp33-abi3-macosx_14_0_arm64 - cp33-abi3-macosx_14_0_universal2 - cp33-abi3-macosx_13_0_arm64 - cp33-abi3-macosx_13_0_universal2 - cp33-abi3-macosx_12_0_arm64 - cp33-abi3-macosx_12_0_universal2 - cp33-abi3-macosx_11_0_arm64 - cp33-abi3-macosx_11_0_universal2 - cp33-abi3-macosx_10_16_universal2 - cp33-abi3-macosx_10_15_universal2 - cp33-abi3-macosx_10_14_universal2 - cp33-abi3-macosx_10_13_universal2 - cp33-abi3-macosx_10_12_universal2 - cp33-abi3-macosx_10_11_universal2 - cp33-abi3-macosx_10_10_universal2 - cp33-abi3-macosx_10_9_universal2 - cp33-abi3-macosx_10_8_universal2 - cp33-abi3-macosx_10_7_universal2 - cp33-abi3-macosx_10_6_universal2 - cp33-abi3-macosx_10_5_universal2 - cp33-abi3-macosx_10_4_universal2 - cp32-abi3-macosx_14_0_arm64 - cp32-abi3-macosx_14_0_universal2 - cp32-abi3-macosx_13_0_arm64 - cp32-abi3-macosx_13_0_universal2 - cp32-abi3-macosx_12_0_arm64 - cp32-abi3-macosx_12_0_universal2 - cp32-abi3-macosx_11_0_arm64 - cp32-abi3-macosx_11_0_universal2 - cp32-abi3-macosx_10_16_universal2 - cp32-abi3-macosx_10_15_universal2 - cp32-abi3-macosx_10_14_universal2 - cp32-abi3-macosx_10_13_universal2 - cp32-abi3-macosx_10_12_universal2 - cp32-abi3-macosx_10_11_universal2 - cp32-abi3-macosx_10_10_universal2 - cp32-abi3-macosx_10_9_universal2 - cp32-abi3-macosx_10_8_universal2 - cp32-abi3-macosx_10_7_universal2 - cp32-abi3-macosx_10_6_universal2 - cp32-abi3-macosx_10_5_universal2 - cp32-abi3-macosx_10_4_universal2 - py39-none-macosx_14_0_arm64 - py39-none-macosx_14_0_universal2 - py39-none-macosx_13_0_arm64 - py39-none-macosx_13_0_universal2 - py39-none-macosx_12_0_arm64 - py39-none-macosx_12_0_universal2 - py39-none-macosx_11_0_arm64 - py39-none-macosx_11_0_universal2 - py39-none-macosx_10_16_universal2 - py39-none-macosx_10_15_universal2 - py39-none-macosx_10_14_universal2 - py39-none-macosx_10_13_universal2 - py39-none-macosx_10_12_universal2 - py39-none-macosx_10_11_universal2 - py39-none-macosx_10_10_universal2 - py39-none-macosx_10_9_universal2 - py39-none-macosx_10_8_universal2 - py39-none-macosx_10_7_universal2 - py39-none-macosx_10_6_universal2 - py39-none-macosx_10_5_universal2 - py39-none-macosx_10_4_universal2 - py3-none-macosx_14_0_arm64 - py3-none-macosx_14_0_universal2 - py3-none-macosx_13_0_arm64 - py3-none-macosx_13_0_universal2 - py3-none-macosx_12_0_arm64 - py3-none-macosx_12_0_universal2 - py3-none-macosx_11_0_arm64 - py3-none-macosx_11_0_universal2 - py3-none-macosx_10_16_universal2 - py3-none-macosx_10_15_universal2 - py3-none-macosx_10_14_universal2 - py3-none-macosx_10_13_universal2 - py3-none-macosx_10_12_universal2 - py3-none-macosx_10_11_universal2 - py3-none-macosx_10_10_universal2 - py3-none-macosx_10_9_universal2 - py3-none-macosx_10_8_universal2 - py3-none-macosx_10_7_universal2 - py3-none-macosx_10_6_universal2 - py3-none-macosx_10_5_universal2 - py3-none-macosx_10_4_universal2 - py38-none-macosx_14_0_arm64 - py38-none-macosx_14_0_universal2 - py38-none-macosx_13_0_arm64 - py38-none-macosx_13_0_universal2 - py38-none-macosx_12_0_arm64 - py38-none-macosx_12_0_universal2 - py38-none-macosx_11_0_arm64 - py38-none-macosx_11_0_universal2 - py38-none-macosx_10_16_universal2 - py38-none-macosx_10_15_universal2 - py38-none-macosx_10_14_universal2 - py38-none-macosx_10_13_universal2 - py38-none-macosx_10_12_universal2 - py38-none-macosx_10_11_universal2 - py38-none-macosx_10_10_universal2 - py38-none-macosx_10_9_universal2 - py38-none-macosx_10_8_universal2 - py38-none-macosx_10_7_universal2 - py38-none-macosx_10_6_universal2 - py38-none-macosx_10_5_universal2 - py38-none-macosx_10_4_universal2 - py37-none-macosx_14_0_arm64 - py37-none-macosx_14_0_universal2 - py37-none-macosx_13_0_arm64 - py37-none-macosx_13_0_universal2 - py37-none-macosx_12_0_arm64 - py37-none-macosx_12_0_universal2 - py37-none-macosx_11_0_arm64 - py37-none-macosx_11_0_universal2 - py37-none-macosx_10_16_universal2 - py37-none-macosx_10_15_universal2 - py37-none-macosx_10_14_universal2 - py37-none-macosx_10_13_universal2 - py37-none-macosx_10_12_universal2 - py37-none-macosx_10_11_universal2 - py37-none-macosx_10_10_universal2 - py37-none-macosx_10_9_universal2 - py37-none-macosx_10_8_universal2 - py37-none-macosx_10_7_universal2 - py37-none-macosx_10_6_universal2 - py37-none-macosx_10_5_universal2 - py37-none-macosx_10_4_universal2 - py36-none-macosx_14_0_arm64 - py36-none-macosx_14_0_universal2 - py36-none-macosx_13_0_arm64 - py36-none-macosx_13_0_universal2 - py36-none-macosx_12_0_arm64 - py36-none-macosx_12_0_universal2 - py36-none-macosx_11_0_arm64 - py36-none-macosx_11_0_universal2 - py36-none-macosx_10_16_universal2 - py36-none-macosx_10_15_universal2 - py36-none-macosx_10_14_universal2 - py36-none-macosx_10_13_universal2 - py36-none-macosx_10_12_universal2 - py36-none-macosx_10_11_universal2 - py36-none-macosx_10_10_universal2 - py36-none-macosx_10_9_universal2 - py36-none-macosx_10_8_universal2 - py36-none-macosx_10_7_universal2 - py36-none-macosx_10_6_universal2 - py36-none-macosx_10_5_universal2 - py36-none-macosx_10_4_universal2 - py35-none-macosx_14_0_arm64 - py35-none-macosx_14_0_universal2 - py35-none-macosx_13_0_arm64 - py35-none-macosx_13_0_universal2 - py35-none-macosx_12_0_arm64 - py35-none-macosx_12_0_universal2 - py35-none-macosx_11_0_arm64 - py35-none-macosx_11_0_universal2 - py35-none-macosx_10_16_universal2 - py35-none-macosx_10_15_universal2 - py35-none-macosx_10_14_universal2 - py35-none-macosx_10_13_universal2 - py35-none-macosx_10_12_universal2 - py35-none-macosx_10_11_universal2 - py35-none-macosx_10_10_universal2 - py35-none-macosx_10_9_universal2 - py35-none-macosx_10_8_universal2 - py35-none-macosx_10_7_universal2 - py35-none-macosx_10_6_universal2 - py35-none-macosx_10_5_universal2 - py35-none-macosx_10_4_universal2 - py34-none-macosx_14_0_arm64 - py34-none-macosx_14_0_universal2 - py34-none-macosx_13_0_arm64 - py34-none-macosx_13_0_universal2 - py34-none-macosx_12_0_arm64 - py34-none-macosx_12_0_universal2 - py34-none-macosx_11_0_arm64 - py34-none-macosx_11_0_universal2 - py34-none-macosx_10_16_universal2 - py34-none-macosx_10_15_universal2 - py34-none-macosx_10_14_universal2 - py34-none-macosx_10_13_universal2 - py34-none-macosx_10_12_universal2 - py34-none-macosx_10_11_universal2 - py34-none-macosx_10_10_universal2 - py34-none-macosx_10_9_universal2 - py34-none-macosx_10_8_universal2 - py34-none-macosx_10_7_universal2 - py34-none-macosx_10_6_universal2 - py34-none-macosx_10_5_universal2 - py34-none-macosx_10_4_universal2 - py33-none-macosx_14_0_arm64 - py33-none-macosx_14_0_universal2 - py33-none-macosx_13_0_arm64 - py33-none-macosx_13_0_universal2 - py33-none-macosx_12_0_arm64 - py33-none-macosx_12_0_universal2 - py33-none-macosx_11_0_arm64 - py33-none-macosx_11_0_universal2 - py33-none-macosx_10_16_universal2 - py33-none-macosx_10_15_universal2 - py33-none-macosx_10_14_universal2 - py33-none-macosx_10_13_universal2 - py33-none-macosx_10_12_universal2 - py33-none-macosx_10_11_universal2 - py33-none-macosx_10_10_universal2 - py33-none-macosx_10_9_universal2 - py33-none-macosx_10_8_universal2 - py33-none-macosx_10_7_universal2 - py33-none-macosx_10_6_universal2 - py33-none-macosx_10_5_universal2 - py33-none-macosx_10_4_universal2 - py32-none-macosx_14_0_arm64 - py32-none-macosx_14_0_universal2 - py32-none-macosx_13_0_arm64 - py32-none-macosx_13_0_universal2 - py32-none-macosx_12_0_arm64 - py32-none-macosx_12_0_universal2 - py32-none-macosx_11_0_arm64 - py32-none-macosx_11_0_universal2 - py32-none-macosx_10_16_universal2 - py32-none-macosx_10_15_universal2 - py32-none-macosx_10_14_universal2 - py32-none-macosx_10_13_universal2 - py32-none-macosx_10_12_universal2 - py32-none-macosx_10_11_universal2 - py32-none-macosx_10_10_universal2 - py32-none-macosx_10_9_universal2 - py32-none-macosx_10_8_universal2 - py32-none-macosx_10_7_universal2 - py32-none-macosx_10_6_universal2 - py32-none-macosx_10_5_universal2 - py32-none-macosx_10_4_universal2 - py31-none-macosx_14_0_arm64 - py31-none-macosx_14_0_universal2 - py31-none-macosx_13_0_arm64 - py31-none-macosx_13_0_universal2 - py31-none-macosx_12_0_arm64 - py31-none-macosx_12_0_universal2 - py31-none-macosx_11_0_arm64 - py31-none-macosx_11_0_universal2 - py31-none-macosx_10_16_universal2 - py31-none-macosx_10_15_universal2 - py31-none-macosx_10_14_universal2 - py31-none-macosx_10_13_universal2 - py31-none-macosx_10_12_universal2 - py31-none-macosx_10_11_universal2 - py31-none-macosx_10_10_universal2 - py31-none-macosx_10_9_universal2 - py31-none-macosx_10_8_universal2 - py31-none-macosx_10_7_universal2 - py31-none-macosx_10_6_universal2 - py31-none-macosx_10_5_universal2 - py31-none-macosx_10_4_universal2 - py30-none-macosx_14_0_arm64 - py30-none-macosx_14_0_universal2 - py30-none-macosx_13_0_arm64 - py30-none-macosx_13_0_universal2 - py30-none-macosx_12_0_arm64 - py30-none-macosx_12_0_universal2 - py30-none-macosx_11_0_arm64 - py30-none-macosx_11_0_universal2 - py30-none-macosx_10_16_universal2 - py30-none-macosx_10_15_universal2 - py30-none-macosx_10_14_universal2 - py30-none-macosx_10_13_universal2 - py30-none-macosx_10_12_universal2 - py30-none-macosx_10_11_universal2 - py30-none-macosx_10_10_universal2 - py30-none-macosx_10_9_universal2 - py30-none-macosx_10_8_universal2 - py30-none-macosx_10_7_universal2 - py30-none-macosx_10_6_universal2 - py30-none-macosx_10_5_universal2 - py30-none-macosx_10_4_universal2 - cp39-none-any - py39-none-any - py3-none-any - py38-none-any - py37-none-any - py36-none-any - py35-none-any - py34-none-any - py33-none-any - py32-none-any - py31-none-any - py30-none-any - "### - ); - } -} +mod tests; diff --git a/crates/uv-platform-tags/src/tags/tests.rs b/crates/uv-platform-tags/src/tags/tests.rs new file mode 100644 index 000000000..fca79b948 --- /dev/null +++ b/crates/uv-platform-tags/src/tags/tests.rs @@ -0,0 +1,1530 @@ +use insta::{assert_debug_snapshot, assert_snapshot}; + +use super::*; + +/// Check platform tag ordering. +/// The list is displayed in decreasing priority. +/// +/// A reference list can be generated with: +/// ```text +/// $ python -c "from packaging import tags; [print(tag) for tag in tags.platform_tags()]"` +/// ```` +#[test] +fn test_platform_tags_manylinux() { + let tags = compatible_tags(&Platform::new( + Os::Manylinux { + major: 2, + minor: 20, + }, + Arch::X86_64, + )) + .unwrap(); + assert_debug_snapshot!( + tags, + @r###" + [ + "manylinux_2_20_x86_64", + "manylinux_2_19_x86_64", + "manylinux_2_18_x86_64", + "manylinux_2_17_x86_64", + "manylinux2014_x86_64", + "manylinux_2_16_x86_64", + "manylinux_2_15_x86_64", + "manylinux_2_14_x86_64", + "manylinux_2_13_x86_64", + "manylinux_2_12_x86_64", + "manylinux2010_x86_64", + "manylinux_2_11_x86_64", + "manylinux_2_10_x86_64", + "manylinux_2_9_x86_64", + "manylinux_2_8_x86_64", + "manylinux_2_7_x86_64", + "manylinux_2_6_x86_64", + "manylinux_2_5_x86_64", + "manylinux1_x86_64", + "linux_x86_64", + ] + "### + ); +} + +#[test] +fn test_platform_tags_macos() { + let tags = compatible_tags(&Platform::new( + Os::Macos { + major: 21, + minor: 6, + }, + Arch::X86_64, + )) + .unwrap(); + assert_debug_snapshot!( + tags, + @r###" + [ + "macosx_21_0_x86_64", + "macosx_21_0_intel", + "macosx_21_0_fat64", + "macosx_21_0_fat32", + "macosx_21_0_universal2", + "macosx_21_0_universal", + "macosx_20_0_x86_64", + "macosx_20_0_intel", + "macosx_20_0_fat64", + "macosx_20_0_fat32", + "macosx_20_0_universal2", + "macosx_20_0_universal", + "macosx_19_0_x86_64", + "macosx_19_0_intel", + "macosx_19_0_fat64", + "macosx_19_0_fat32", + "macosx_19_0_universal2", + "macosx_19_0_universal", + "macosx_18_0_x86_64", + "macosx_18_0_intel", + "macosx_18_0_fat64", + "macosx_18_0_fat32", + "macosx_18_0_universal2", + "macosx_18_0_universal", + "macosx_17_0_x86_64", + "macosx_17_0_intel", + "macosx_17_0_fat64", + "macosx_17_0_fat32", + "macosx_17_0_universal2", + "macosx_17_0_universal", + "macosx_16_0_x86_64", + "macosx_16_0_intel", + "macosx_16_0_fat64", + "macosx_16_0_fat32", + "macosx_16_0_universal2", + "macosx_16_0_universal", + "macosx_15_0_x86_64", + "macosx_15_0_intel", + "macosx_15_0_fat64", + "macosx_15_0_fat32", + "macosx_15_0_universal2", + "macosx_15_0_universal", + "macosx_14_0_x86_64", + "macosx_14_0_intel", + "macosx_14_0_fat64", + "macosx_14_0_fat32", + "macosx_14_0_universal2", + "macosx_14_0_universal", + "macosx_13_0_x86_64", + "macosx_13_0_intel", + "macosx_13_0_fat64", + "macosx_13_0_fat32", + "macosx_13_0_universal2", + "macosx_13_0_universal", + "macosx_12_0_x86_64", + "macosx_12_0_intel", + "macosx_12_0_fat64", + "macosx_12_0_fat32", + "macosx_12_0_universal2", + "macosx_12_0_universal", + "macosx_11_0_x86_64", + "macosx_11_0_intel", + "macosx_11_0_fat64", + "macosx_11_0_fat32", + "macosx_11_0_universal2", + "macosx_11_0_universal", + "macosx_10_16_x86_64", + "macosx_10_16_intel", + "macosx_10_16_fat64", + "macosx_10_16_fat32", + "macosx_10_16_universal2", + "macosx_10_16_universal", + "macosx_10_15_x86_64", + "macosx_10_15_intel", + "macosx_10_15_fat64", + "macosx_10_15_fat32", + "macosx_10_15_universal2", + "macosx_10_15_universal", + "macosx_10_14_x86_64", + "macosx_10_14_intel", + "macosx_10_14_fat64", + "macosx_10_14_fat32", + "macosx_10_14_universal2", + "macosx_10_14_universal", + "macosx_10_13_x86_64", + "macosx_10_13_intel", + "macosx_10_13_fat64", + "macosx_10_13_fat32", + "macosx_10_13_universal2", + "macosx_10_13_universal", + "macosx_10_12_x86_64", + "macosx_10_12_intel", + "macosx_10_12_fat64", + "macosx_10_12_fat32", + "macosx_10_12_universal2", + "macosx_10_12_universal", + "macosx_10_11_x86_64", + "macosx_10_11_intel", + "macosx_10_11_fat64", + "macosx_10_11_fat32", + "macosx_10_11_universal2", + "macosx_10_11_universal", + "macosx_10_10_x86_64", + "macosx_10_10_intel", + "macosx_10_10_fat64", + "macosx_10_10_fat32", + "macosx_10_10_universal2", + "macosx_10_10_universal", + "macosx_10_9_x86_64", + "macosx_10_9_intel", + "macosx_10_9_fat64", + "macosx_10_9_fat32", + "macosx_10_9_universal2", + "macosx_10_9_universal", + "macosx_10_8_x86_64", + "macosx_10_8_intel", + "macosx_10_8_fat64", + "macosx_10_8_fat32", + "macosx_10_8_universal2", + "macosx_10_8_universal", + "macosx_10_7_x86_64", + "macosx_10_7_intel", + "macosx_10_7_fat64", + "macosx_10_7_fat32", + "macosx_10_7_universal2", + "macosx_10_7_universal", + "macosx_10_6_x86_64", + "macosx_10_6_intel", + "macosx_10_6_fat64", + "macosx_10_6_fat32", + "macosx_10_6_universal2", + "macosx_10_6_universal", + "macosx_10_5_x86_64", + "macosx_10_5_intel", + "macosx_10_5_fat64", + "macosx_10_5_fat32", + "macosx_10_5_universal2", + "macosx_10_5_universal", + "macosx_10_4_x86_64", + "macosx_10_4_intel", + "macosx_10_4_fat64", + "macosx_10_4_fat32", + "macosx_10_4_universal2", + "macosx_10_4_universal", + ] + "### + ); + + let tags = compatible_tags(&Platform::new( + Os::Macos { + major: 14, + minor: 0, + }, + Arch::X86_64, + )) + .unwrap(); + assert_debug_snapshot!( + tags, + @r###" + [ + "macosx_14_0_x86_64", + "macosx_14_0_intel", + "macosx_14_0_fat64", + "macosx_14_0_fat32", + "macosx_14_0_universal2", + "macosx_14_0_universal", + "macosx_13_0_x86_64", + "macosx_13_0_intel", + "macosx_13_0_fat64", + "macosx_13_0_fat32", + "macosx_13_0_universal2", + "macosx_13_0_universal", + "macosx_12_0_x86_64", + "macosx_12_0_intel", + "macosx_12_0_fat64", + "macosx_12_0_fat32", + "macosx_12_0_universal2", + "macosx_12_0_universal", + "macosx_11_0_x86_64", + "macosx_11_0_intel", + "macosx_11_0_fat64", + "macosx_11_0_fat32", + "macosx_11_0_universal2", + "macosx_11_0_universal", + "macosx_10_16_x86_64", + "macosx_10_16_intel", + "macosx_10_16_fat64", + "macosx_10_16_fat32", + "macosx_10_16_universal2", + "macosx_10_16_universal", + "macosx_10_15_x86_64", + "macosx_10_15_intel", + "macosx_10_15_fat64", + "macosx_10_15_fat32", + "macosx_10_15_universal2", + "macosx_10_15_universal", + "macosx_10_14_x86_64", + "macosx_10_14_intel", + "macosx_10_14_fat64", + "macosx_10_14_fat32", + "macosx_10_14_universal2", + "macosx_10_14_universal", + "macosx_10_13_x86_64", + "macosx_10_13_intel", + "macosx_10_13_fat64", + "macosx_10_13_fat32", + "macosx_10_13_universal2", + "macosx_10_13_universal", + "macosx_10_12_x86_64", + "macosx_10_12_intel", + "macosx_10_12_fat64", + "macosx_10_12_fat32", + "macosx_10_12_universal2", + "macosx_10_12_universal", + "macosx_10_11_x86_64", + "macosx_10_11_intel", + "macosx_10_11_fat64", + "macosx_10_11_fat32", + "macosx_10_11_universal2", + "macosx_10_11_universal", + "macosx_10_10_x86_64", + "macosx_10_10_intel", + "macosx_10_10_fat64", + "macosx_10_10_fat32", + "macosx_10_10_universal2", + "macosx_10_10_universal", + "macosx_10_9_x86_64", + "macosx_10_9_intel", + "macosx_10_9_fat64", + "macosx_10_9_fat32", + "macosx_10_9_universal2", + "macosx_10_9_universal", + "macosx_10_8_x86_64", + "macosx_10_8_intel", + "macosx_10_8_fat64", + "macosx_10_8_fat32", + "macosx_10_8_universal2", + "macosx_10_8_universal", + "macosx_10_7_x86_64", + "macosx_10_7_intel", + "macosx_10_7_fat64", + "macosx_10_7_fat32", + "macosx_10_7_universal2", + "macosx_10_7_universal", + "macosx_10_6_x86_64", + "macosx_10_6_intel", + "macosx_10_6_fat64", + "macosx_10_6_fat32", + "macosx_10_6_universal2", + "macosx_10_6_universal", + "macosx_10_5_x86_64", + "macosx_10_5_intel", + "macosx_10_5_fat64", + "macosx_10_5_fat32", + "macosx_10_5_universal2", + "macosx_10_5_universal", + "macosx_10_4_x86_64", + "macosx_10_4_intel", + "macosx_10_4_fat64", + "macosx_10_4_fat32", + "macosx_10_4_universal2", + "macosx_10_4_universal", + ] + "### + ); + + let tags = compatible_tags(&Platform::new( + Os::Macos { + major: 10, + minor: 6, + }, + Arch::X86_64, + )) + .unwrap(); + assert_debug_snapshot!( + tags, + @r###" + [ + "macosx_10_6_x86_64", + "macosx_10_6_intel", + "macosx_10_6_fat64", + "macosx_10_6_fat32", + "macosx_10_6_universal2", + "macosx_10_6_universal", + "macosx_10_5_x86_64", + "macosx_10_5_intel", + "macosx_10_5_fat64", + "macosx_10_5_fat32", + "macosx_10_5_universal2", + "macosx_10_5_universal", + "macosx_10_4_x86_64", + "macosx_10_4_intel", + "macosx_10_4_fat64", + "macosx_10_4_fat32", + "macosx_10_4_universal2", + "macosx_10_4_universal", + ] + "### + ); +} + +/// Ensure the tags returned do not include the `manylinux` tags +/// when `manylinux_incompatible` is set to `false`. +#[test] +fn test_manylinux_incompatible() { + let tags = Tags::from_env( + &Platform::new( + Os::Manylinux { + major: 2, + minor: 28, + }, + Arch::X86_64, + ), + (3, 9), + "cpython", + (3, 9), + false, + false, + ) + .unwrap(); + assert_snapshot!( + tags, + @r###" + cp39-cp39-linux_x86_64 + cp39-abi3-linux_x86_64 + cp39-none-linux_x86_64 + cp38-abi3-linux_x86_64 + cp37-abi3-linux_x86_64 + cp36-abi3-linux_x86_64 + cp35-abi3-linux_x86_64 + cp34-abi3-linux_x86_64 + cp33-abi3-linux_x86_64 + cp32-abi3-linux_x86_64 + py39-none-linux_x86_64 + py3-none-linux_x86_64 + py38-none-linux_x86_64 + py37-none-linux_x86_64 + py36-none-linux_x86_64 + py35-none-linux_x86_64 + py34-none-linux_x86_64 + py33-none-linux_x86_64 + py32-none-linux_x86_64 + py31-none-linux_x86_64 + py30-none-linux_x86_64 + cp39-none-any + py39-none-any + py3-none-any + py38-none-any + py37-none-any + py36-none-any + py35-none-any + py34-none-any + py33-none-any + py32-none-any + py31-none-any + py30-none-any + "###); +} + +/// Check full tag ordering. +/// The list is displayed in decreasing priority. +/// +/// A reference list can be generated with: +/// ```text +/// $ python -c "from packaging import tags; [print(tag) for tag in tags.sys_tags()]"` +/// ``` +#[test] +fn test_system_tags_manylinux() { + let tags = Tags::from_env( + &Platform::new( + Os::Manylinux { + major: 2, + minor: 28, + }, + Arch::X86_64, + ), + (3, 9), + "cpython", + (3, 9), + true, + false, + ) + .unwrap(); + assert_snapshot!( + tags, + @r###" + cp39-cp39-manylinux_2_28_x86_64 + cp39-cp39-manylinux_2_27_x86_64 + cp39-cp39-manylinux_2_26_x86_64 + cp39-cp39-manylinux_2_25_x86_64 + cp39-cp39-manylinux_2_24_x86_64 + cp39-cp39-manylinux_2_23_x86_64 + cp39-cp39-manylinux_2_22_x86_64 + cp39-cp39-manylinux_2_21_x86_64 + cp39-cp39-manylinux_2_20_x86_64 + cp39-cp39-manylinux_2_19_x86_64 + cp39-cp39-manylinux_2_18_x86_64 + cp39-cp39-manylinux_2_17_x86_64 + cp39-cp39-manylinux2014_x86_64 + cp39-cp39-manylinux_2_16_x86_64 + cp39-cp39-manylinux_2_15_x86_64 + cp39-cp39-manylinux_2_14_x86_64 + cp39-cp39-manylinux_2_13_x86_64 + cp39-cp39-manylinux_2_12_x86_64 + cp39-cp39-manylinux2010_x86_64 + cp39-cp39-manylinux_2_11_x86_64 + cp39-cp39-manylinux_2_10_x86_64 + cp39-cp39-manylinux_2_9_x86_64 + cp39-cp39-manylinux_2_8_x86_64 + cp39-cp39-manylinux_2_7_x86_64 + cp39-cp39-manylinux_2_6_x86_64 + cp39-cp39-manylinux_2_5_x86_64 + cp39-cp39-manylinux1_x86_64 + cp39-cp39-linux_x86_64 + cp39-abi3-manylinux_2_28_x86_64 + cp39-abi3-manylinux_2_27_x86_64 + cp39-abi3-manylinux_2_26_x86_64 + cp39-abi3-manylinux_2_25_x86_64 + cp39-abi3-manylinux_2_24_x86_64 + cp39-abi3-manylinux_2_23_x86_64 + cp39-abi3-manylinux_2_22_x86_64 + cp39-abi3-manylinux_2_21_x86_64 + cp39-abi3-manylinux_2_20_x86_64 + cp39-abi3-manylinux_2_19_x86_64 + cp39-abi3-manylinux_2_18_x86_64 + cp39-abi3-manylinux_2_17_x86_64 + cp39-abi3-manylinux2014_x86_64 + cp39-abi3-manylinux_2_16_x86_64 + cp39-abi3-manylinux_2_15_x86_64 + cp39-abi3-manylinux_2_14_x86_64 + cp39-abi3-manylinux_2_13_x86_64 + cp39-abi3-manylinux_2_12_x86_64 + cp39-abi3-manylinux2010_x86_64 + cp39-abi3-manylinux_2_11_x86_64 + cp39-abi3-manylinux_2_10_x86_64 + cp39-abi3-manylinux_2_9_x86_64 + cp39-abi3-manylinux_2_8_x86_64 + cp39-abi3-manylinux_2_7_x86_64 + cp39-abi3-manylinux_2_6_x86_64 + cp39-abi3-manylinux_2_5_x86_64 + cp39-abi3-manylinux1_x86_64 + cp39-abi3-linux_x86_64 + cp39-none-manylinux_2_28_x86_64 + cp39-none-manylinux_2_27_x86_64 + cp39-none-manylinux_2_26_x86_64 + cp39-none-manylinux_2_25_x86_64 + cp39-none-manylinux_2_24_x86_64 + cp39-none-manylinux_2_23_x86_64 + cp39-none-manylinux_2_22_x86_64 + cp39-none-manylinux_2_21_x86_64 + cp39-none-manylinux_2_20_x86_64 + cp39-none-manylinux_2_19_x86_64 + cp39-none-manylinux_2_18_x86_64 + cp39-none-manylinux_2_17_x86_64 + cp39-none-manylinux2014_x86_64 + cp39-none-manylinux_2_16_x86_64 + cp39-none-manylinux_2_15_x86_64 + cp39-none-manylinux_2_14_x86_64 + cp39-none-manylinux_2_13_x86_64 + cp39-none-manylinux_2_12_x86_64 + cp39-none-manylinux2010_x86_64 + cp39-none-manylinux_2_11_x86_64 + cp39-none-manylinux_2_10_x86_64 + cp39-none-manylinux_2_9_x86_64 + cp39-none-manylinux_2_8_x86_64 + cp39-none-manylinux_2_7_x86_64 + cp39-none-manylinux_2_6_x86_64 + cp39-none-manylinux_2_5_x86_64 + cp39-none-manylinux1_x86_64 + cp39-none-linux_x86_64 + cp38-abi3-manylinux_2_28_x86_64 + cp38-abi3-manylinux_2_27_x86_64 + cp38-abi3-manylinux_2_26_x86_64 + cp38-abi3-manylinux_2_25_x86_64 + cp38-abi3-manylinux_2_24_x86_64 + cp38-abi3-manylinux_2_23_x86_64 + cp38-abi3-manylinux_2_22_x86_64 + cp38-abi3-manylinux_2_21_x86_64 + cp38-abi3-manylinux_2_20_x86_64 + cp38-abi3-manylinux_2_19_x86_64 + cp38-abi3-manylinux_2_18_x86_64 + cp38-abi3-manylinux_2_17_x86_64 + cp38-abi3-manylinux2014_x86_64 + cp38-abi3-manylinux_2_16_x86_64 + cp38-abi3-manylinux_2_15_x86_64 + cp38-abi3-manylinux_2_14_x86_64 + cp38-abi3-manylinux_2_13_x86_64 + cp38-abi3-manylinux_2_12_x86_64 + cp38-abi3-manylinux2010_x86_64 + cp38-abi3-manylinux_2_11_x86_64 + cp38-abi3-manylinux_2_10_x86_64 + cp38-abi3-manylinux_2_9_x86_64 + cp38-abi3-manylinux_2_8_x86_64 + cp38-abi3-manylinux_2_7_x86_64 + cp38-abi3-manylinux_2_6_x86_64 + cp38-abi3-manylinux_2_5_x86_64 + cp38-abi3-manylinux1_x86_64 + cp38-abi3-linux_x86_64 + cp37-abi3-manylinux_2_28_x86_64 + cp37-abi3-manylinux_2_27_x86_64 + cp37-abi3-manylinux_2_26_x86_64 + cp37-abi3-manylinux_2_25_x86_64 + cp37-abi3-manylinux_2_24_x86_64 + cp37-abi3-manylinux_2_23_x86_64 + cp37-abi3-manylinux_2_22_x86_64 + cp37-abi3-manylinux_2_21_x86_64 + cp37-abi3-manylinux_2_20_x86_64 + cp37-abi3-manylinux_2_19_x86_64 + cp37-abi3-manylinux_2_18_x86_64 + cp37-abi3-manylinux_2_17_x86_64 + cp37-abi3-manylinux2014_x86_64 + cp37-abi3-manylinux_2_16_x86_64 + cp37-abi3-manylinux_2_15_x86_64 + cp37-abi3-manylinux_2_14_x86_64 + cp37-abi3-manylinux_2_13_x86_64 + cp37-abi3-manylinux_2_12_x86_64 + cp37-abi3-manylinux2010_x86_64 + cp37-abi3-manylinux_2_11_x86_64 + cp37-abi3-manylinux_2_10_x86_64 + cp37-abi3-manylinux_2_9_x86_64 + cp37-abi3-manylinux_2_8_x86_64 + cp37-abi3-manylinux_2_7_x86_64 + cp37-abi3-manylinux_2_6_x86_64 + cp37-abi3-manylinux_2_5_x86_64 + cp37-abi3-manylinux1_x86_64 + cp37-abi3-linux_x86_64 + cp36-abi3-manylinux_2_28_x86_64 + cp36-abi3-manylinux_2_27_x86_64 + cp36-abi3-manylinux_2_26_x86_64 + cp36-abi3-manylinux_2_25_x86_64 + cp36-abi3-manylinux_2_24_x86_64 + cp36-abi3-manylinux_2_23_x86_64 + cp36-abi3-manylinux_2_22_x86_64 + cp36-abi3-manylinux_2_21_x86_64 + cp36-abi3-manylinux_2_20_x86_64 + cp36-abi3-manylinux_2_19_x86_64 + cp36-abi3-manylinux_2_18_x86_64 + cp36-abi3-manylinux_2_17_x86_64 + cp36-abi3-manylinux2014_x86_64 + cp36-abi3-manylinux_2_16_x86_64 + cp36-abi3-manylinux_2_15_x86_64 + cp36-abi3-manylinux_2_14_x86_64 + cp36-abi3-manylinux_2_13_x86_64 + cp36-abi3-manylinux_2_12_x86_64 + cp36-abi3-manylinux2010_x86_64 + cp36-abi3-manylinux_2_11_x86_64 + cp36-abi3-manylinux_2_10_x86_64 + cp36-abi3-manylinux_2_9_x86_64 + cp36-abi3-manylinux_2_8_x86_64 + cp36-abi3-manylinux_2_7_x86_64 + cp36-abi3-manylinux_2_6_x86_64 + cp36-abi3-manylinux_2_5_x86_64 + cp36-abi3-manylinux1_x86_64 + cp36-abi3-linux_x86_64 + cp35-abi3-manylinux_2_28_x86_64 + cp35-abi3-manylinux_2_27_x86_64 + cp35-abi3-manylinux_2_26_x86_64 + cp35-abi3-manylinux_2_25_x86_64 + cp35-abi3-manylinux_2_24_x86_64 + cp35-abi3-manylinux_2_23_x86_64 + cp35-abi3-manylinux_2_22_x86_64 + cp35-abi3-manylinux_2_21_x86_64 + cp35-abi3-manylinux_2_20_x86_64 + cp35-abi3-manylinux_2_19_x86_64 + cp35-abi3-manylinux_2_18_x86_64 + cp35-abi3-manylinux_2_17_x86_64 + cp35-abi3-manylinux2014_x86_64 + cp35-abi3-manylinux_2_16_x86_64 + cp35-abi3-manylinux_2_15_x86_64 + cp35-abi3-manylinux_2_14_x86_64 + cp35-abi3-manylinux_2_13_x86_64 + cp35-abi3-manylinux_2_12_x86_64 + cp35-abi3-manylinux2010_x86_64 + cp35-abi3-manylinux_2_11_x86_64 + cp35-abi3-manylinux_2_10_x86_64 + cp35-abi3-manylinux_2_9_x86_64 + cp35-abi3-manylinux_2_8_x86_64 + cp35-abi3-manylinux_2_7_x86_64 + cp35-abi3-manylinux_2_6_x86_64 + cp35-abi3-manylinux_2_5_x86_64 + cp35-abi3-manylinux1_x86_64 + cp35-abi3-linux_x86_64 + cp34-abi3-manylinux_2_28_x86_64 + cp34-abi3-manylinux_2_27_x86_64 + cp34-abi3-manylinux_2_26_x86_64 + cp34-abi3-manylinux_2_25_x86_64 + cp34-abi3-manylinux_2_24_x86_64 + cp34-abi3-manylinux_2_23_x86_64 + cp34-abi3-manylinux_2_22_x86_64 + cp34-abi3-manylinux_2_21_x86_64 + cp34-abi3-manylinux_2_20_x86_64 + cp34-abi3-manylinux_2_19_x86_64 + cp34-abi3-manylinux_2_18_x86_64 + cp34-abi3-manylinux_2_17_x86_64 + cp34-abi3-manylinux2014_x86_64 + cp34-abi3-manylinux_2_16_x86_64 + cp34-abi3-manylinux_2_15_x86_64 + cp34-abi3-manylinux_2_14_x86_64 + cp34-abi3-manylinux_2_13_x86_64 + cp34-abi3-manylinux_2_12_x86_64 + cp34-abi3-manylinux2010_x86_64 + cp34-abi3-manylinux_2_11_x86_64 + cp34-abi3-manylinux_2_10_x86_64 + cp34-abi3-manylinux_2_9_x86_64 + cp34-abi3-manylinux_2_8_x86_64 + cp34-abi3-manylinux_2_7_x86_64 + cp34-abi3-manylinux_2_6_x86_64 + cp34-abi3-manylinux_2_5_x86_64 + cp34-abi3-manylinux1_x86_64 + cp34-abi3-linux_x86_64 + cp33-abi3-manylinux_2_28_x86_64 + cp33-abi3-manylinux_2_27_x86_64 + cp33-abi3-manylinux_2_26_x86_64 + cp33-abi3-manylinux_2_25_x86_64 + cp33-abi3-manylinux_2_24_x86_64 + cp33-abi3-manylinux_2_23_x86_64 + cp33-abi3-manylinux_2_22_x86_64 + cp33-abi3-manylinux_2_21_x86_64 + cp33-abi3-manylinux_2_20_x86_64 + cp33-abi3-manylinux_2_19_x86_64 + cp33-abi3-manylinux_2_18_x86_64 + cp33-abi3-manylinux_2_17_x86_64 + cp33-abi3-manylinux2014_x86_64 + cp33-abi3-manylinux_2_16_x86_64 + cp33-abi3-manylinux_2_15_x86_64 + cp33-abi3-manylinux_2_14_x86_64 + cp33-abi3-manylinux_2_13_x86_64 + cp33-abi3-manylinux_2_12_x86_64 + cp33-abi3-manylinux2010_x86_64 + cp33-abi3-manylinux_2_11_x86_64 + cp33-abi3-manylinux_2_10_x86_64 + cp33-abi3-manylinux_2_9_x86_64 + cp33-abi3-manylinux_2_8_x86_64 + cp33-abi3-manylinux_2_7_x86_64 + cp33-abi3-manylinux_2_6_x86_64 + cp33-abi3-manylinux_2_5_x86_64 + cp33-abi3-manylinux1_x86_64 + cp33-abi3-linux_x86_64 + cp32-abi3-manylinux_2_28_x86_64 + cp32-abi3-manylinux_2_27_x86_64 + cp32-abi3-manylinux_2_26_x86_64 + cp32-abi3-manylinux_2_25_x86_64 + cp32-abi3-manylinux_2_24_x86_64 + cp32-abi3-manylinux_2_23_x86_64 + cp32-abi3-manylinux_2_22_x86_64 + cp32-abi3-manylinux_2_21_x86_64 + cp32-abi3-manylinux_2_20_x86_64 + cp32-abi3-manylinux_2_19_x86_64 + cp32-abi3-manylinux_2_18_x86_64 + cp32-abi3-manylinux_2_17_x86_64 + cp32-abi3-manylinux2014_x86_64 + cp32-abi3-manylinux_2_16_x86_64 + cp32-abi3-manylinux_2_15_x86_64 + cp32-abi3-manylinux_2_14_x86_64 + cp32-abi3-manylinux_2_13_x86_64 + cp32-abi3-manylinux_2_12_x86_64 + cp32-abi3-manylinux2010_x86_64 + cp32-abi3-manylinux_2_11_x86_64 + cp32-abi3-manylinux_2_10_x86_64 + cp32-abi3-manylinux_2_9_x86_64 + cp32-abi3-manylinux_2_8_x86_64 + cp32-abi3-manylinux_2_7_x86_64 + cp32-abi3-manylinux_2_6_x86_64 + cp32-abi3-manylinux_2_5_x86_64 + cp32-abi3-manylinux1_x86_64 + cp32-abi3-linux_x86_64 + py39-none-manylinux_2_28_x86_64 + py39-none-manylinux_2_27_x86_64 + py39-none-manylinux_2_26_x86_64 + py39-none-manylinux_2_25_x86_64 + py39-none-manylinux_2_24_x86_64 + py39-none-manylinux_2_23_x86_64 + py39-none-manylinux_2_22_x86_64 + py39-none-manylinux_2_21_x86_64 + py39-none-manylinux_2_20_x86_64 + py39-none-manylinux_2_19_x86_64 + py39-none-manylinux_2_18_x86_64 + py39-none-manylinux_2_17_x86_64 + py39-none-manylinux2014_x86_64 + py39-none-manylinux_2_16_x86_64 + py39-none-manylinux_2_15_x86_64 + py39-none-manylinux_2_14_x86_64 + py39-none-manylinux_2_13_x86_64 + py39-none-manylinux_2_12_x86_64 + py39-none-manylinux2010_x86_64 + py39-none-manylinux_2_11_x86_64 + py39-none-manylinux_2_10_x86_64 + py39-none-manylinux_2_9_x86_64 + py39-none-manylinux_2_8_x86_64 + py39-none-manylinux_2_7_x86_64 + py39-none-manylinux_2_6_x86_64 + py39-none-manylinux_2_5_x86_64 + py39-none-manylinux1_x86_64 + py39-none-linux_x86_64 + py3-none-manylinux_2_28_x86_64 + py3-none-manylinux_2_27_x86_64 + py3-none-manylinux_2_26_x86_64 + py3-none-manylinux_2_25_x86_64 + py3-none-manylinux_2_24_x86_64 + py3-none-manylinux_2_23_x86_64 + py3-none-manylinux_2_22_x86_64 + py3-none-manylinux_2_21_x86_64 + py3-none-manylinux_2_20_x86_64 + py3-none-manylinux_2_19_x86_64 + py3-none-manylinux_2_18_x86_64 + py3-none-manylinux_2_17_x86_64 + py3-none-manylinux2014_x86_64 + py3-none-manylinux_2_16_x86_64 + py3-none-manylinux_2_15_x86_64 + py3-none-manylinux_2_14_x86_64 + py3-none-manylinux_2_13_x86_64 + py3-none-manylinux_2_12_x86_64 + py3-none-manylinux2010_x86_64 + py3-none-manylinux_2_11_x86_64 + py3-none-manylinux_2_10_x86_64 + py3-none-manylinux_2_9_x86_64 + py3-none-manylinux_2_8_x86_64 + py3-none-manylinux_2_7_x86_64 + py3-none-manylinux_2_6_x86_64 + py3-none-manylinux_2_5_x86_64 + py3-none-manylinux1_x86_64 + py3-none-linux_x86_64 + py38-none-manylinux_2_28_x86_64 + py38-none-manylinux_2_27_x86_64 + py38-none-manylinux_2_26_x86_64 + py38-none-manylinux_2_25_x86_64 + py38-none-manylinux_2_24_x86_64 + py38-none-manylinux_2_23_x86_64 + py38-none-manylinux_2_22_x86_64 + py38-none-manylinux_2_21_x86_64 + py38-none-manylinux_2_20_x86_64 + py38-none-manylinux_2_19_x86_64 + py38-none-manylinux_2_18_x86_64 + py38-none-manylinux_2_17_x86_64 + py38-none-manylinux2014_x86_64 + py38-none-manylinux_2_16_x86_64 + py38-none-manylinux_2_15_x86_64 + py38-none-manylinux_2_14_x86_64 + py38-none-manylinux_2_13_x86_64 + py38-none-manylinux_2_12_x86_64 + py38-none-manylinux2010_x86_64 + py38-none-manylinux_2_11_x86_64 + py38-none-manylinux_2_10_x86_64 + py38-none-manylinux_2_9_x86_64 + py38-none-manylinux_2_8_x86_64 + py38-none-manylinux_2_7_x86_64 + py38-none-manylinux_2_6_x86_64 + py38-none-manylinux_2_5_x86_64 + py38-none-manylinux1_x86_64 + py38-none-linux_x86_64 + py37-none-manylinux_2_28_x86_64 + py37-none-manylinux_2_27_x86_64 + py37-none-manylinux_2_26_x86_64 + py37-none-manylinux_2_25_x86_64 + py37-none-manylinux_2_24_x86_64 + py37-none-manylinux_2_23_x86_64 + py37-none-manylinux_2_22_x86_64 + py37-none-manylinux_2_21_x86_64 + py37-none-manylinux_2_20_x86_64 + py37-none-manylinux_2_19_x86_64 + py37-none-manylinux_2_18_x86_64 + py37-none-manylinux_2_17_x86_64 + py37-none-manylinux2014_x86_64 + py37-none-manylinux_2_16_x86_64 + py37-none-manylinux_2_15_x86_64 + py37-none-manylinux_2_14_x86_64 + py37-none-manylinux_2_13_x86_64 + py37-none-manylinux_2_12_x86_64 + py37-none-manylinux2010_x86_64 + py37-none-manylinux_2_11_x86_64 + py37-none-manylinux_2_10_x86_64 + py37-none-manylinux_2_9_x86_64 + py37-none-manylinux_2_8_x86_64 + py37-none-manylinux_2_7_x86_64 + py37-none-manylinux_2_6_x86_64 + py37-none-manylinux_2_5_x86_64 + py37-none-manylinux1_x86_64 + py37-none-linux_x86_64 + py36-none-manylinux_2_28_x86_64 + py36-none-manylinux_2_27_x86_64 + py36-none-manylinux_2_26_x86_64 + py36-none-manylinux_2_25_x86_64 + py36-none-manylinux_2_24_x86_64 + py36-none-manylinux_2_23_x86_64 + py36-none-manylinux_2_22_x86_64 + py36-none-manylinux_2_21_x86_64 + py36-none-manylinux_2_20_x86_64 + py36-none-manylinux_2_19_x86_64 + py36-none-manylinux_2_18_x86_64 + py36-none-manylinux_2_17_x86_64 + py36-none-manylinux2014_x86_64 + py36-none-manylinux_2_16_x86_64 + py36-none-manylinux_2_15_x86_64 + py36-none-manylinux_2_14_x86_64 + py36-none-manylinux_2_13_x86_64 + py36-none-manylinux_2_12_x86_64 + py36-none-manylinux2010_x86_64 + py36-none-manylinux_2_11_x86_64 + py36-none-manylinux_2_10_x86_64 + py36-none-manylinux_2_9_x86_64 + py36-none-manylinux_2_8_x86_64 + py36-none-manylinux_2_7_x86_64 + py36-none-manylinux_2_6_x86_64 + py36-none-manylinux_2_5_x86_64 + py36-none-manylinux1_x86_64 + py36-none-linux_x86_64 + py35-none-manylinux_2_28_x86_64 + py35-none-manylinux_2_27_x86_64 + py35-none-manylinux_2_26_x86_64 + py35-none-manylinux_2_25_x86_64 + py35-none-manylinux_2_24_x86_64 + py35-none-manylinux_2_23_x86_64 + py35-none-manylinux_2_22_x86_64 + py35-none-manylinux_2_21_x86_64 + py35-none-manylinux_2_20_x86_64 + py35-none-manylinux_2_19_x86_64 + py35-none-manylinux_2_18_x86_64 + py35-none-manylinux_2_17_x86_64 + py35-none-manylinux2014_x86_64 + py35-none-manylinux_2_16_x86_64 + py35-none-manylinux_2_15_x86_64 + py35-none-manylinux_2_14_x86_64 + py35-none-manylinux_2_13_x86_64 + py35-none-manylinux_2_12_x86_64 + py35-none-manylinux2010_x86_64 + py35-none-manylinux_2_11_x86_64 + py35-none-manylinux_2_10_x86_64 + py35-none-manylinux_2_9_x86_64 + py35-none-manylinux_2_8_x86_64 + py35-none-manylinux_2_7_x86_64 + py35-none-manylinux_2_6_x86_64 + py35-none-manylinux_2_5_x86_64 + py35-none-manylinux1_x86_64 + py35-none-linux_x86_64 + py34-none-manylinux_2_28_x86_64 + py34-none-manylinux_2_27_x86_64 + py34-none-manylinux_2_26_x86_64 + py34-none-manylinux_2_25_x86_64 + py34-none-manylinux_2_24_x86_64 + py34-none-manylinux_2_23_x86_64 + py34-none-manylinux_2_22_x86_64 + py34-none-manylinux_2_21_x86_64 + py34-none-manylinux_2_20_x86_64 + py34-none-manylinux_2_19_x86_64 + py34-none-manylinux_2_18_x86_64 + py34-none-manylinux_2_17_x86_64 + py34-none-manylinux2014_x86_64 + py34-none-manylinux_2_16_x86_64 + py34-none-manylinux_2_15_x86_64 + py34-none-manylinux_2_14_x86_64 + py34-none-manylinux_2_13_x86_64 + py34-none-manylinux_2_12_x86_64 + py34-none-manylinux2010_x86_64 + py34-none-manylinux_2_11_x86_64 + py34-none-manylinux_2_10_x86_64 + py34-none-manylinux_2_9_x86_64 + py34-none-manylinux_2_8_x86_64 + py34-none-manylinux_2_7_x86_64 + py34-none-manylinux_2_6_x86_64 + py34-none-manylinux_2_5_x86_64 + py34-none-manylinux1_x86_64 + py34-none-linux_x86_64 + py33-none-manylinux_2_28_x86_64 + py33-none-manylinux_2_27_x86_64 + py33-none-manylinux_2_26_x86_64 + py33-none-manylinux_2_25_x86_64 + py33-none-manylinux_2_24_x86_64 + py33-none-manylinux_2_23_x86_64 + py33-none-manylinux_2_22_x86_64 + py33-none-manylinux_2_21_x86_64 + py33-none-manylinux_2_20_x86_64 + py33-none-manylinux_2_19_x86_64 + py33-none-manylinux_2_18_x86_64 + py33-none-manylinux_2_17_x86_64 + py33-none-manylinux2014_x86_64 + py33-none-manylinux_2_16_x86_64 + py33-none-manylinux_2_15_x86_64 + py33-none-manylinux_2_14_x86_64 + py33-none-manylinux_2_13_x86_64 + py33-none-manylinux_2_12_x86_64 + py33-none-manylinux2010_x86_64 + py33-none-manylinux_2_11_x86_64 + py33-none-manylinux_2_10_x86_64 + py33-none-manylinux_2_9_x86_64 + py33-none-manylinux_2_8_x86_64 + py33-none-manylinux_2_7_x86_64 + py33-none-manylinux_2_6_x86_64 + py33-none-manylinux_2_5_x86_64 + py33-none-manylinux1_x86_64 + py33-none-linux_x86_64 + py32-none-manylinux_2_28_x86_64 + py32-none-manylinux_2_27_x86_64 + py32-none-manylinux_2_26_x86_64 + py32-none-manylinux_2_25_x86_64 + py32-none-manylinux_2_24_x86_64 + py32-none-manylinux_2_23_x86_64 + py32-none-manylinux_2_22_x86_64 + py32-none-manylinux_2_21_x86_64 + py32-none-manylinux_2_20_x86_64 + py32-none-manylinux_2_19_x86_64 + py32-none-manylinux_2_18_x86_64 + py32-none-manylinux_2_17_x86_64 + py32-none-manylinux2014_x86_64 + py32-none-manylinux_2_16_x86_64 + py32-none-manylinux_2_15_x86_64 + py32-none-manylinux_2_14_x86_64 + py32-none-manylinux_2_13_x86_64 + py32-none-manylinux_2_12_x86_64 + py32-none-manylinux2010_x86_64 + py32-none-manylinux_2_11_x86_64 + py32-none-manylinux_2_10_x86_64 + py32-none-manylinux_2_9_x86_64 + py32-none-manylinux_2_8_x86_64 + py32-none-manylinux_2_7_x86_64 + py32-none-manylinux_2_6_x86_64 + py32-none-manylinux_2_5_x86_64 + py32-none-manylinux1_x86_64 + py32-none-linux_x86_64 + py31-none-manylinux_2_28_x86_64 + py31-none-manylinux_2_27_x86_64 + py31-none-manylinux_2_26_x86_64 + py31-none-manylinux_2_25_x86_64 + py31-none-manylinux_2_24_x86_64 + py31-none-manylinux_2_23_x86_64 + py31-none-manylinux_2_22_x86_64 + py31-none-manylinux_2_21_x86_64 + py31-none-manylinux_2_20_x86_64 + py31-none-manylinux_2_19_x86_64 + py31-none-manylinux_2_18_x86_64 + py31-none-manylinux_2_17_x86_64 + py31-none-manylinux2014_x86_64 + py31-none-manylinux_2_16_x86_64 + py31-none-manylinux_2_15_x86_64 + py31-none-manylinux_2_14_x86_64 + py31-none-manylinux_2_13_x86_64 + py31-none-manylinux_2_12_x86_64 + py31-none-manylinux2010_x86_64 + py31-none-manylinux_2_11_x86_64 + py31-none-manylinux_2_10_x86_64 + py31-none-manylinux_2_9_x86_64 + py31-none-manylinux_2_8_x86_64 + py31-none-manylinux_2_7_x86_64 + py31-none-manylinux_2_6_x86_64 + py31-none-manylinux_2_5_x86_64 + py31-none-manylinux1_x86_64 + py31-none-linux_x86_64 + py30-none-manylinux_2_28_x86_64 + py30-none-manylinux_2_27_x86_64 + py30-none-manylinux_2_26_x86_64 + py30-none-manylinux_2_25_x86_64 + py30-none-manylinux_2_24_x86_64 + py30-none-manylinux_2_23_x86_64 + py30-none-manylinux_2_22_x86_64 + py30-none-manylinux_2_21_x86_64 + py30-none-manylinux_2_20_x86_64 + py30-none-manylinux_2_19_x86_64 + py30-none-manylinux_2_18_x86_64 + py30-none-manylinux_2_17_x86_64 + py30-none-manylinux2014_x86_64 + py30-none-manylinux_2_16_x86_64 + py30-none-manylinux_2_15_x86_64 + py30-none-manylinux_2_14_x86_64 + py30-none-manylinux_2_13_x86_64 + py30-none-manylinux_2_12_x86_64 + py30-none-manylinux2010_x86_64 + py30-none-manylinux_2_11_x86_64 + py30-none-manylinux_2_10_x86_64 + py30-none-manylinux_2_9_x86_64 + py30-none-manylinux_2_8_x86_64 + py30-none-manylinux_2_7_x86_64 + py30-none-manylinux_2_6_x86_64 + py30-none-manylinux_2_5_x86_64 + py30-none-manylinux1_x86_64 + py30-none-linux_x86_64 + cp39-none-any + py39-none-any + py3-none-any + py38-none-any + py37-none-any + py36-none-any + py35-none-any + py34-none-any + py33-none-any + py32-none-any + py31-none-any + py30-none-any + "### + ); +} + +#[test] +fn test_system_tags_macos() { + let tags = Tags::from_env( + &Platform::new( + Os::Macos { + major: 14, + minor: 0, + }, + Arch::Aarch64, + ), + (3, 9), + "cpython", + (3, 9), + false, + false, + ) + .unwrap(); + assert_snapshot!( + tags, + @r###" + cp39-cp39-macosx_14_0_arm64 + cp39-cp39-macosx_14_0_universal2 + cp39-cp39-macosx_13_0_arm64 + cp39-cp39-macosx_13_0_universal2 + cp39-cp39-macosx_12_0_arm64 + cp39-cp39-macosx_12_0_universal2 + cp39-cp39-macosx_11_0_arm64 + cp39-cp39-macosx_11_0_universal2 + cp39-cp39-macosx_10_16_universal2 + cp39-cp39-macosx_10_15_universal2 + cp39-cp39-macosx_10_14_universal2 + cp39-cp39-macosx_10_13_universal2 + cp39-cp39-macosx_10_12_universal2 + cp39-cp39-macosx_10_11_universal2 + cp39-cp39-macosx_10_10_universal2 + cp39-cp39-macosx_10_9_universal2 + cp39-cp39-macosx_10_8_universal2 + cp39-cp39-macosx_10_7_universal2 + cp39-cp39-macosx_10_6_universal2 + cp39-cp39-macosx_10_5_universal2 + cp39-cp39-macosx_10_4_universal2 + cp39-abi3-macosx_14_0_arm64 + cp39-abi3-macosx_14_0_universal2 + cp39-abi3-macosx_13_0_arm64 + cp39-abi3-macosx_13_0_universal2 + cp39-abi3-macosx_12_0_arm64 + cp39-abi3-macosx_12_0_universal2 + cp39-abi3-macosx_11_0_arm64 + cp39-abi3-macosx_11_0_universal2 + cp39-abi3-macosx_10_16_universal2 + cp39-abi3-macosx_10_15_universal2 + cp39-abi3-macosx_10_14_universal2 + cp39-abi3-macosx_10_13_universal2 + cp39-abi3-macosx_10_12_universal2 + cp39-abi3-macosx_10_11_universal2 + cp39-abi3-macosx_10_10_universal2 + cp39-abi3-macosx_10_9_universal2 + cp39-abi3-macosx_10_8_universal2 + cp39-abi3-macosx_10_7_universal2 + cp39-abi3-macosx_10_6_universal2 + cp39-abi3-macosx_10_5_universal2 + cp39-abi3-macosx_10_4_universal2 + cp39-none-macosx_14_0_arm64 + cp39-none-macosx_14_0_universal2 + cp39-none-macosx_13_0_arm64 + cp39-none-macosx_13_0_universal2 + cp39-none-macosx_12_0_arm64 + cp39-none-macosx_12_0_universal2 + cp39-none-macosx_11_0_arm64 + cp39-none-macosx_11_0_universal2 + cp39-none-macosx_10_16_universal2 + cp39-none-macosx_10_15_universal2 + cp39-none-macosx_10_14_universal2 + cp39-none-macosx_10_13_universal2 + cp39-none-macosx_10_12_universal2 + cp39-none-macosx_10_11_universal2 + cp39-none-macosx_10_10_universal2 + cp39-none-macosx_10_9_universal2 + cp39-none-macosx_10_8_universal2 + cp39-none-macosx_10_7_universal2 + cp39-none-macosx_10_6_universal2 + cp39-none-macosx_10_5_universal2 + cp39-none-macosx_10_4_universal2 + cp38-abi3-macosx_14_0_arm64 + cp38-abi3-macosx_14_0_universal2 + cp38-abi3-macosx_13_0_arm64 + cp38-abi3-macosx_13_0_universal2 + cp38-abi3-macosx_12_0_arm64 + cp38-abi3-macosx_12_0_universal2 + cp38-abi3-macosx_11_0_arm64 + cp38-abi3-macosx_11_0_universal2 + cp38-abi3-macosx_10_16_universal2 + cp38-abi3-macosx_10_15_universal2 + cp38-abi3-macosx_10_14_universal2 + cp38-abi3-macosx_10_13_universal2 + cp38-abi3-macosx_10_12_universal2 + cp38-abi3-macosx_10_11_universal2 + cp38-abi3-macosx_10_10_universal2 + cp38-abi3-macosx_10_9_universal2 + cp38-abi3-macosx_10_8_universal2 + cp38-abi3-macosx_10_7_universal2 + cp38-abi3-macosx_10_6_universal2 + cp38-abi3-macosx_10_5_universal2 + cp38-abi3-macosx_10_4_universal2 + cp37-abi3-macosx_14_0_arm64 + cp37-abi3-macosx_14_0_universal2 + cp37-abi3-macosx_13_0_arm64 + cp37-abi3-macosx_13_0_universal2 + cp37-abi3-macosx_12_0_arm64 + cp37-abi3-macosx_12_0_universal2 + cp37-abi3-macosx_11_0_arm64 + cp37-abi3-macosx_11_0_universal2 + cp37-abi3-macosx_10_16_universal2 + cp37-abi3-macosx_10_15_universal2 + cp37-abi3-macosx_10_14_universal2 + cp37-abi3-macosx_10_13_universal2 + cp37-abi3-macosx_10_12_universal2 + cp37-abi3-macosx_10_11_universal2 + cp37-abi3-macosx_10_10_universal2 + cp37-abi3-macosx_10_9_universal2 + cp37-abi3-macosx_10_8_universal2 + cp37-abi3-macosx_10_7_universal2 + cp37-abi3-macosx_10_6_universal2 + cp37-abi3-macosx_10_5_universal2 + cp37-abi3-macosx_10_4_universal2 + cp36-abi3-macosx_14_0_arm64 + cp36-abi3-macosx_14_0_universal2 + cp36-abi3-macosx_13_0_arm64 + cp36-abi3-macosx_13_0_universal2 + cp36-abi3-macosx_12_0_arm64 + cp36-abi3-macosx_12_0_universal2 + cp36-abi3-macosx_11_0_arm64 + cp36-abi3-macosx_11_0_universal2 + cp36-abi3-macosx_10_16_universal2 + cp36-abi3-macosx_10_15_universal2 + cp36-abi3-macosx_10_14_universal2 + cp36-abi3-macosx_10_13_universal2 + cp36-abi3-macosx_10_12_universal2 + cp36-abi3-macosx_10_11_universal2 + cp36-abi3-macosx_10_10_universal2 + cp36-abi3-macosx_10_9_universal2 + cp36-abi3-macosx_10_8_universal2 + cp36-abi3-macosx_10_7_universal2 + cp36-abi3-macosx_10_6_universal2 + cp36-abi3-macosx_10_5_universal2 + cp36-abi3-macosx_10_4_universal2 + cp35-abi3-macosx_14_0_arm64 + cp35-abi3-macosx_14_0_universal2 + cp35-abi3-macosx_13_0_arm64 + cp35-abi3-macosx_13_0_universal2 + cp35-abi3-macosx_12_0_arm64 + cp35-abi3-macosx_12_0_universal2 + cp35-abi3-macosx_11_0_arm64 + cp35-abi3-macosx_11_0_universal2 + cp35-abi3-macosx_10_16_universal2 + cp35-abi3-macosx_10_15_universal2 + cp35-abi3-macosx_10_14_universal2 + cp35-abi3-macosx_10_13_universal2 + cp35-abi3-macosx_10_12_universal2 + cp35-abi3-macosx_10_11_universal2 + cp35-abi3-macosx_10_10_universal2 + cp35-abi3-macosx_10_9_universal2 + cp35-abi3-macosx_10_8_universal2 + cp35-abi3-macosx_10_7_universal2 + cp35-abi3-macosx_10_6_universal2 + cp35-abi3-macosx_10_5_universal2 + cp35-abi3-macosx_10_4_universal2 + cp34-abi3-macosx_14_0_arm64 + cp34-abi3-macosx_14_0_universal2 + cp34-abi3-macosx_13_0_arm64 + cp34-abi3-macosx_13_0_universal2 + cp34-abi3-macosx_12_0_arm64 + cp34-abi3-macosx_12_0_universal2 + cp34-abi3-macosx_11_0_arm64 + cp34-abi3-macosx_11_0_universal2 + cp34-abi3-macosx_10_16_universal2 + cp34-abi3-macosx_10_15_universal2 + cp34-abi3-macosx_10_14_universal2 + cp34-abi3-macosx_10_13_universal2 + cp34-abi3-macosx_10_12_universal2 + cp34-abi3-macosx_10_11_universal2 + cp34-abi3-macosx_10_10_universal2 + cp34-abi3-macosx_10_9_universal2 + cp34-abi3-macosx_10_8_universal2 + cp34-abi3-macosx_10_7_universal2 + cp34-abi3-macosx_10_6_universal2 + cp34-abi3-macosx_10_5_universal2 + cp34-abi3-macosx_10_4_universal2 + cp33-abi3-macosx_14_0_arm64 + cp33-abi3-macosx_14_0_universal2 + cp33-abi3-macosx_13_0_arm64 + cp33-abi3-macosx_13_0_universal2 + cp33-abi3-macosx_12_0_arm64 + cp33-abi3-macosx_12_0_universal2 + cp33-abi3-macosx_11_0_arm64 + cp33-abi3-macosx_11_0_universal2 + cp33-abi3-macosx_10_16_universal2 + cp33-abi3-macosx_10_15_universal2 + cp33-abi3-macosx_10_14_universal2 + cp33-abi3-macosx_10_13_universal2 + cp33-abi3-macosx_10_12_universal2 + cp33-abi3-macosx_10_11_universal2 + cp33-abi3-macosx_10_10_universal2 + cp33-abi3-macosx_10_9_universal2 + cp33-abi3-macosx_10_8_universal2 + cp33-abi3-macosx_10_7_universal2 + cp33-abi3-macosx_10_6_universal2 + cp33-abi3-macosx_10_5_universal2 + cp33-abi3-macosx_10_4_universal2 + cp32-abi3-macosx_14_0_arm64 + cp32-abi3-macosx_14_0_universal2 + cp32-abi3-macosx_13_0_arm64 + cp32-abi3-macosx_13_0_universal2 + cp32-abi3-macosx_12_0_arm64 + cp32-abi3-macosx_12_0_universal2 + cp32-abi3-macosx_11_0_arm64 + cp32-abi3-macosx_11_0_universal2 + cp32-abi3-macosx_10_16_universal2 + cp32-abi3-macosx_10_15_universal2 + cp32-abi3-macosx_10_14_universal2 + cp32-abi3-macosx_10_13_universal2 + cp32-abi3-macosx_10_12_universal2 + cp32-abi3-macosx_10_11_universal2 + cp32-abi3-macosx_10_10_universal2 + cp32-abi3-macosx_10_9_universal2 + cp32-abi3-macosx_10_8_universal2 + cp32-abi3-macosx_10_7_universal2 + cp32-abi3-macosx_10_6_universal2 + cp32-abi3-macosx_10_5_universal2 + cp32-abi3-macosx_10_4_universal2 + py39-none-macosx_14_0_arm64 + py39-none-macosx_14_0_universal2 + py39-none-macosx_13_0_arm64 + py39-none-macosx_13_0_universal2 + py39-none-macosx_12_0_arm64 + py39-none-macosx_12_0_universal2 + py39-none-macosx_11_0_arm64 + py39-none-macosx_11_0_universal2 + py39-none-macosx_10_16_universal2 + py39-none-macosx_10_15_universal2 + py39-none-macosx_10_14_universal2 + py39-none-macosx_10_13_universal2 + py39-none-macosx_10_12_universal2 + py39-none-macosx_10_11_universal2 + py39-none-macosx_10_10_universal2 + py39-none-macosx_10_9_universal2 + py39-none-macosx_10_8_universal2 + py39-none-macosx_10_7_universal2 + py39-none-macosx_10_6_universal2 + py39-none-macosx_10_5_universal2 + py39-none-macosx_10_4_universal2 + py3-none-macosx_14_0_arm64 + py3-none-macosx_14_0_universal2 + py3-none-macosx_13_0_arm64 + py3-none-macosx_13_0_universal2 + py3-none-macosx_12_0_arm64 + py3-none-macosx_12_0_universal2 + py3-none-macosx_11_0_arm64 + py3-none-macosx_11_0_universal2 + py3-none-macosx_10_16_universal2 + py3-none-macosx_10_15_universal2 + py3-none-macosx_10_14_universal2 + py3-none-macosx_10_13_universal2 + py3-none-macosx_10_12_universal2 + py3-none-macosx_10_11_universal2 + py3-none-macosx_10_10_universal2 + py3-none-macosx_10_9_universal2 + py3-none-macosx_10_8_universal2 + py3-none-macosx_10_7_universal2 + py3-none-macosx_10_6_universal2 + py3-none-macosx_10_5_universal2 + py3-none-macosx_10_4_universal2 + py38-none-macosx_14_0_arm64 + py38-none-macosx_14_0_universal2 + py38-none-macosx_13_0_arm64 + py38-none-macosx_13_0_universal2 + py38-none-macosx_12_0_arm64 + py38-none-macosx_12_0_universal2 + py38-none-macosx_11_0_arm64 + py38-none-macosx_11_0_universal2 + py38-none-macosx_10_16_universal2 + py38-none-macosx_10_15_universal2 + py38-none-macosx_10_14_universal2 + py38-none-macosx_10_13_universal2 + py38-none-macosx_10_12_universal2 + py38-none-macosx_10_11_universal2 + py38-none-macosx_10_10_universal2 + py38-none-macosx_10_9_universal2 + py38-none-macosx_10_8_universal2 + py38-none-macosx_10_7_universal2 + py38-none-macosx_10_6_universal2 + py38-none-macosx_10_5_universal2 + py38-none-macosx_10_4_universal2 + py37-none-macosx_14_0_arm64 + py37-none-macosx_14_0_universal2 + py37-none-macosx_13_0_arm64 + py37-none-macosx_13_0_universal2 + py37-none-macosx_12_0_arm64 + py37-none-macosx_12_0_universal2 + py37-none-macosx_11_0_arm64 + py37-none-macosx_11_0_universal2 + py37-none-macosx_10_16_universal2 + py37-none-macosx_10_15_universal2 + py37-none-macosx_10_14_universal2 + py37-none-macosx_10_13_universal2 + py37-none-macosx_10_12_universal2 + py37-none-macosx_10_11_universal2 + py37-none-macosx_10_10_universal2 + py37-none-macosx_10_9_universal2 + py37-none-macosx_10_8_universal2 + py37-none-macosx_10_7_universal2 + py37-none-macosx_10_6_universal2 + py37-none-macosx_10_5_universal2 + py37-none-macosx_10_4_universal2 + py36-none-macosx_14_0_arm64 + py36-none-macosx_14_0_universal2 + py36-none-macosx_13_0_arm64 + py36-none-macosx_13_0_universal2 + py36-none-macosx_12_0_arm64 + py36-none-macosx_12_0_universal2 + py36-none-macosx_11_0_arm64 + py36-none-macosx_11_0_universal2 + py36-none-macosx_10_16_universal2 + py36-none-macosx_10_15_universal2 + py36-none-macosx_10_14_universal2 + py36-none-macosx_10_13_universal2 + py36-none-macosx_10_12_universal2 + py36-none-macosx_10_11_universal2 + py36-none-macosx_10_10_universal2 + py36-none-macosx_10_9_universal2 + py36-none-macosx_10_8_universal2 + py36-none-macosx_10_7_universal2 + py36-none-macosx_10_6_universal2 + py36-none-macosx_10_5_universal2 + py36-none-macosx_10_4_universal2 + py35-none-macosx_14_0_arm64 + py35-none-macosx_14_0_universal2 + py35-none-macosx_13_0_arm64 + py35-none-macosx_13_0_universal2 + py35-none-macosx_12_0_arm64 + py35-none-macosx_12_0_universal2 + py35-none-macosx_11_0_arm64 + py35-none-macosx_11_0_universal2 + py35-none-macosx_10_16_universal2 + py35-none-macosx_10_15_universal2 + py35-none-macosx_10_14_universal2 + py35-none-macosx_10_13_universal2 + py35-none-macosx_10_12_universal2 + py35-none-macosx_10_11_universal2 + py35-none-macosx_10_10_universal2 + py35-none-macosx_10_9_universal2 + py35-none-macosx_10_8_universal2 + py35-none-macosx_10_7_universal2 + py35-none-macosx_10_6_universal2 + py35-none-macosx_10_5_universal2 + py35-none-macosx_10_4_universal2 + py34-none-macosx_14_0_arm64 + py34-none-macosx_14_0_universal2 + py34-none-macosx_13_0_arm64 + py34-none-macosx_13_0_universal2 + py34-none-macosx_12_0_arm64 + py34-none-macosx_12_0_universal2 + py34-none-macosx_11_0_arm64 + py34-none-macosx_11_0_universal2 + py34-none-macosx_10_16_universal2 + py34-none-macosx_10_15_universal2 + py34-none-macosx_10_14_universal2 + py34-none-macosx_10_13_universal2 + py34-none-macosx_10_12_universal2 + py34-none-macosx_10_11_universal2 + py34-none-macosx_10_10_universal2 + py34-none-macosx_10_9_universal2 + py34-none-macosx_10_8_universal2 + py34-none-macosx_10_7_universal2 + py34-none-macosx_10_6_universal2 + py34-none-macosx_10_5_universal2 + py34-none-macosx_10_4_universal2 + py33-none-macosx_14_0_arm64 + py33-none-macosx_14_0_universal2 + py33-none-macosx_13_0_arm64 + py33-none-macosx_13_0_universal2 + py33-none-macosx_12_0_arm64 + py33-none-macosx_12_0_universal2 + py33-none-macosx_11_0_arm64 + py33-none-macosx_11_0_universal2 + py33-none-macosx_10_16_universal2 + py33-none-macosx_10_15_universal2 + py33-none-macosx_10_14_universal2 + py33-none-macosx_10_13_universal2 + py33-none-macosx_10_12_universal2 + py33-none-macosx_10_11_universal2 + py33-none-macosx_10_10_universal2 + py33-none-macosx_10_9_universal2 + py33-none-macosx_10_8_universal2 + py33-none-macosx_10_7_universal2 + py33-none-macosx_10_6_universal2 + py33-none-macosx_10_5_universal2 + py33-none-macosx_10_4_universal2 + py32-none-macosx_14_0_arm64 + py32-none-macosx_14_0_universal2 + py32-none-macosx_13_0_arm64 + py32-none-macosx_13_0_universal2 + py32-none-macosx_12_0_arm64 + py32-none-macosx_12_0_universal2 + py32-none-macosx_11_0_arm64 + py32-none-macosx_11_0_universal2 + py32-none-macosx_10_16_universal2 + py32-none-macosx_10_15_universal2 + py32-none-macosx_10_14_universal2 + py32-none-macosx_10_13_universal2 + py32-none-macosx_10_12_universal2 + py32-none-macosx_10_11_universal2 + py32-none-macosx_10_10_universal2 + py32-none-macosx_10_9_universal2 + py32-none-macosx_10_8_universal2 + py32-none-macosx_10_7_universal2 + py32-none-macosx_10_6_universal2 + py32-none-macosx_10_5_universal2 + py32-none-macosx_10_4_universal2 + py31-none-macosx_14_0_arm64 + py31-none-macosx_14_0_universal2 + py31-none-macosx_13_0_arm64 + py31-none-macosx_13_0_universal2 + py31-none-macosx_12_0_arm64 + py31-none-macosx_12_0_universal2 + py31-none-macosx_11_0_arm64 + py31-none-macosx_11_0_universal2 + py31-none-macosx_10_16_universal2 + py31-none-macosx_10_15_universal2 + py31-none-macosx_10_14_universal2 + py31-none-macosx_10_13_universal2 + py31-none-macosx_10_12_universal2 + py31-none-macosx_10_11_universal2 + py31-none-macosx_10_10_universal2 + py31-none-macosx_10_9_universal2 + py31-none-macosx_10_8_universal2 + py31-none-macosx_10_7_universal2 + py31-none-macosx_10_6_universal2 + py31-none-macosx_10_5_universal2 + py31-none-macosx_10_4_universal2 + py30-none-macosx_14_0_arm64 + py30-none-macosx_14_0_universal2 + py30-none-macosx_13_0_arm64 + py30-none-macosx_13_0_universal2 + py30-none-macosx_12_0_arm64 + py30-none-macosx_12_0_universal2 + py30-none-macosx_11_0_arm64 + py30-none-macosx_11_0_universal2 + py30-none-macosx_10_16_universal2 + py30-none-macosx_10_15_universal2 + py30-none-macosx_10_14_universal2 + py30-none-macosx_10_13_universal2 + py30-none-macosx_10_12_universal2 + py30-none-macosx_10_11_universal2 + py30-none-macosx_10_10_universal2 + py30-none-macosx_10_9_universal2 + py30-none-macosx_10_8_universal2 + py30-none-macosx_10_7_universal2 + py30-none-macosx_10_6_universal2 + py30-none-macosx_10_5_universal2 + py30-none-macosx_10_4_universal2 + cp39-none-any + py39-none-any + py3-none-any + py38-none-any + py37-none-any + py36-none-any + py35-none-any + py34-none-any + py33-none-any + py32-none-any + py31-none-any + py30-none-any + "### + ); +} diff --git a/crates/uv-pubgrub/Cargo.toml b/crates/uv-pubgrub/Cargo.toml index 907856145..8c9753112 100644 --- a/crates/uv-pubgrub/Cargo.toml +++ b/crates/uv-pubgrub/Cargo.toml @@ -4,6 +4,9 @@ version = "0.0.1" edition = "2021" description = "Common uv pubgrub types." +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-publish/Cargo.toml b/crates/uv-publish/Cargo.toml index 9348924bf..6c640f70a 100644 --- a/crates/uv-publish/Cargo.toml +++ b/crates/uv-publish/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true authors.workspace = true license.workspace = true +[lib] +doctest = false + [dependencies] uv-client = { workspace = true } uv-configuration = { workspace = true } @@ -16,6 +19,7 @@ uv-distribution-filename = { workspace = true } uv-fs = { workspace = true } uv-metadata = { workspace = true } uv-pypi-types = { workspace = true } +uv-static = { workspace = true } uv-warnings = { workspace = true } async-compression = { workspace = true } diff --git a/crates/uv-publish/src/lib.rs b/crates/uv-publish/src/lib.rs index f85643b03..f832079ab 100644 --- a/crates/uv-publish/src/lib.rs +++ b/crates/uv-publish/src/lib.rs @@ -30,6 +30,7 @@ use uv_distribution_filename::{DistFilename, SourceDistExtension, SourceDistFile use uv_fs::{ProgressReader, Simplified}; use uv_metadata::read_metadata_async_seek; use uv_pypi_types::{Metadata23, MetadataError}; +use uv_static::EnvVars; use uv_warnings::{warn_user, warn_user_once}; pub use trusted_publishing::TrustedPublishingToken; @@ -81,8 +82,12 @@ pub enum PublishSendError { ReqwestMiddleware(#[from] reqwest_middleware::Error), #[error("Upload failed with status {0}")] StatusNoBody(StatusCode, #[source] reqwest::Error), - #[error("Upload failed with status code {0}: {1}")] + #[error("Upload failed with status code {0}. Server says: {1}")] Status(StatusCode, String), + #[error("POST requests are not supported by the endpoint, are you using the simple index URL instead of the upload URL?")] + MethodNotAllowedNoBody, + #[error("POST requests are not supported by the endpoint, are you using the simple index URL instead of the upload URL? Server says: {0}")] + MethodNotAllowed(String), /// The registry returned a "403 Forbidden". #[error("Permission denied (status code {0}): {1}")] PermissionDenied(StatusCode, String), @@ -193,9 +198,15 @@ impl PublishSendError { } } +/// Collect the source distributions and wheels for publishing. +/// +/// Returns the path, the raw filename and the parsed filename. The raw filename is a fixup for +/// caused by +/// in combination with +/// pub fn files_for_publishing( paths: Vec, -) -> Result, PublishError> { +) -> Result, PublishError> { let mut seen = FxHashSet::default(); let mut files = Vec::new(); for path in paths { @@ -207,15 +218,19 @@ pub fn files_for_publishing( if !seen.insert(dist.clone()) { continue; } - let Some(filename) = dist.file_name().and_then(|filename| filename.to_str()) else { + let Some(filename) = dist + .file_name() + .and_then(|filename| filename.to_str()) + .map(ToString::to_string) + else { continue; }; if filename == ".gitignore" { continue; } - let filename = DistFilename::try_from_normalized_filename(filename) + let dist_filename = DistFilename::try_from_normalized_filename(&filename) .ok_or_else(|| PublishError::InvalidFilename(dist.clone()))?; - files.push((dist, filename)); + files.push((dist, filename, dist_filename)); } } // TODO(konsti): Should we sort those files, e.g. wheels before sdists because they are more @@ -243,7 +258,7 @@ pub async fn check_trusted_publishing( return Ok(None); } // If we aren't in GitHub Actions, we can't use trusted publishing. - if env::var("GITHUB_ACTIONS") != Ok("true".to_string()) { + if env::var(EnvVars::GITHUB_ACTIONS) != Ok("true".to_string()) { return Ok(None); } // We could check for credentials from the keyring or netrc the auth middleware first, but @@ -262,7 +277,7 @@ pub async fn check_trusted_publishing( } TrustedPublishing::Always => { debug!("Using trusted publishing for GitHub Actions"); - if env::var("GITHUB_ACTIONS") != Ok("true".to_string()) { + if env::var(EnvVars::GITHUB_ACTIONS) != Ok("true".to_string()) { warn_user_once!( "Trusted publishing was requested, but you're not in GitHub Actions." ); @@ -282,6 +297,7 @@ pub async fn check_trusted_publishing( /// Implements a custom retry flow since the request isn't cloneable. pub async fn upload( file: &Path, + raw_filename: &str, filename: &DistFilename, registry: &Url, client: &ClientWithMiddleware, @@ -300,6 +316,7 @@ pub async fn upload( attempt += 1; let (request, idx) = build_request( file, + raw_filename, filename, registry, client, @@ -484,6 +501,7 @@ async fn form_metadata( /// Returns the request and the reporter progress bar id. async fn build_request( file: &Path, + raw_filename: &str, filename: &DistFilename, registry: &Url, client: &ClientWithMiddleware, @@ -505,7 +523,8 @@ async fn build_request( // Stream wrapping puts a static lifetime requirement on the reader (so the request doesn't have // a lifetime) -> callback needs to be static -> reporter reference needs to be Arc'd. let file_reader = Body::wrap_stream(ReaderStream::new(reader)); - let part = Part::stream(file_reader).file_name(filename.to_string()); + // See [`files_for_publishing`] on `raw_filename` + let part = Part::stream(file_reader).file_name(raw_filename.to_string()); form = form.part("content", part); let url = if let Some(username) = username { @@ -577,18 +596,32 @@ async fn handle_response(registry: &Url, response: Response) -> Result Result Result) -> usize { - 0 - } - fn on_download_progress(&self, _id: usize, _inc: u64) {} - fn on_download_complete(&self, _id: usize) {} - } - - /// Snapshot the data we send for an upload request for a source distribution. - #[tokio::test] - async fn upload_request_source_dist() { - let filename = "tqdm-999.0.0.tar.gz"; - let file = PathBuf::from("../../scripts/links/").join(filename); - let filename = DistFilename::try_from_normalized_filename(filename).unwrap(); - - let form_metadata = form_metadata(&file, &filename).await.unwrap(); - - let formatted_metadata = form_metadata - .iter() - .map(|(k, v)| format!("{k}: {v}")) - .join("\n"); - assert_snapshot!(&formatted_metadata, @r###" - :action: file_upload - sha256_digest: 89fa05cffa7f457658373b85de302d24d0c205ceda2819a8739e324b75e9430b - protocol_version: 1 - metadata_version: 2.3 - name: tqdm - version: 999.0.0 - filetype: sdist - pyversion: source - description: # tqdm - - [![PyPI - Version](https://img.shields.io/pypi/v/tqdm.svg)](https://pypi.org/project/tqdm) - [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/tqdm.svg)](https://pypi.org/project/tqdm) - - ----- - - **Table of Contents** - - - [Installation](#installation) - - [License](#license) - - ## Installation - - ```console - pip install tqdm - ``` - - ## License - - `tqdm` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. - - description_content_type: text/markdown - author_email: Charlie Marsh - requires_python: >=3.8 - classifiers: Development Status :: 4 - Beta - classifiers: Programming Language :: Python - classifiers: Programming Language :: Python :: 3.8 - classifiers: Programming Language :: Python :: 3.9 - classifiers: Programming Language :: Python :: 3.10 - classifiers: Programming Language :: Python :: 3.11 - classifiers: Programming Language :: Python :: 3.12 - classifiers: Programming Language :: Python :: Implementation :: CPython - classifiers: Programming Language :: Python :: Implementation :: PyPy - project_urls: Documentation, https://github.com/unknown/tqdm#readme - project_urls: Issues, https://github.com/unknown/tqdm/issues - project_urls: Source, https://github.com/unknown/tqdm - "###); - - let (request, _) = build_request( - &file, - &filename, - &Url::parse("https://example.org/upload").unwrap(), - &BaseClientBuilder::new().build().client(), - Some("ferris"), - Some("F3RR!S"), - &form_metadata, - Arc::new(DummyReporter), - ) - .await - .unwrap(); - - insta::with_settings!({ - filters => [("boundary=[0-9a-f-]+", "boundary=[...]")], - }, { - assert_debug_snapshot!(&request, @r###" - RequestBuilder { - inner: RequestBuilder { - method: POST, - url: Url { - scheme: "https", - cannot_be_a_base: false, - username: "", - password: None, - host: Some( - Domain( - "example.org", - ), - ), - port: None, - path: "/upload", - query: None, - fragment: None, - }, - headers: { - "content-type": "multipart/form-data; boundary=[...]", - "accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7", - "authorization": "Basic ZmVycmlzOkYzUlIhUw==", - }, - }, - .. - } - "###); - }); - } - - /// Snapshot the data we send for an upload request for a wheel. - #[tokio::test] - async fn upload_request_wheel() { - let filename = "tqdm-4.66.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl"; - let file = PathBuf::from("../../scripts/links/").join(filename); - let filename = DistFilename::try_from_normalized_filename(filename).unwrap(); - - let form_metadata = form_metadata(&file, &filename).await.unwrap(); - - let formatted_metadata = form_metadata - .iter() - .map(|(k, v)| format!("{k}: {v}")) - .join("\n"); - assert_snapshot!(&formatted_metadata, @r###" - :action: file_upload - sha256_digest: 0d88ca657bc6b64995ca416e0c59c71af85cc10015d940fa446c42a8b485ee1c - protocol_version: 1 - metadata_version: 2.1 - name: tqdm - version: 4.66.1 - filetype: bdist_wheel - pyversion: py3 - summary: Fast, Extensible Progress Meter - description_content_type: text/x-rst - maintainer_email: tqdm developers - license: MPL-2.0 AND MIT - keywords: progressbar,progressmeter,progress,bar,meter,rate,eta,console,terminal,time - requires_python: >=3.7 - classifiers: Development Status :: 5 - Production/Stable - classifiers: Environment :: Console - classifiers: Environment :: MacOS X - classifiers: Environment :: Other Environment - classifiers: Environment :: Win32 (MS Windows) - classifiers: Environment :: X11 Applications - classifiers: Framework :: IPython - classifiers: Framework :: Jupyter - classifiers: Intended Audience :: Developers - classifiers: Intended Audience :: Education - classifiers: Intended Audience :: End Users/Desktop - classifiers: Intended Audience :: Other Audience - classifiers: Intended Audience :: System Administrators - classifiers: License :: OSI Approved :: MIT License - classifiers: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) - classifiers: Operating System :: MacOS - classifiers: Operating System :: MacOS :: MacOS X - classifiers: Operating System :: Microsoft - classifiers: Operating System :: Microsoft :: MS-DOS - classifiers: Operating System :: Microsoft :: Windows - classifiers: Operating System :: POSIX - classifiers: Operating System :: POSIX :: BSD - classifiers: Operating System :: POSIX :: BSD :: FreeBSD - classifiers: Operating System :: POSIX :: Linux - classifiers: Operating System :: POSIX :: SunOS/Solaris - classifiers: Operating System :: Unix - classifiers: Programming Language :: Python - classifiers: Programming Language :: Python :: 3 - classifiers: Programming Language :: Python :: 3.7 - classifiers: Programming Language :: Python :: 3.8 - classifiers: Programming Language :: Python :: 3.9 - classifiers: Programming Language :: Python :: 3.10 - classifiers: Programming Language :: Python :: 3.11 - classifiers: Programming Language :: Python :: 3 :: Only - classifiers: Programming Language :: Python :: Implementation - classifiers: Programming Language :: Python :: Implementation :: IronPython - classifiers: Programming Language :: Python :: Implementation :: PyPy - classifiers: Programming Language :: Unix Shell - classifiers: Topic :: Desktop Environment - classifiers: Topic :: Education :: Computer Aided Instruction (CAI) - classifiers: Topic :: Education :: Testing - classifiers: Topic :: Office/Business - classifiers: Topic :: Other/Nonlisted Topic - classifiers: Topic :: Software Development :: Build Tools - classifiers: Topic :: Software Development :: Libraries - classifiers: Topic :: Software Development :: Libraries :: Python Modules - classifiers: Topic :: Software Development :: Pre-processors - classifiers: Topic :: Software Development :: User Interfaces - classifiers: Topic :: System :: Installation/Setup - classifiers: Topic :: System :: Logging - classifiers: Topic :: System :: Monitoring - classifiers: Topic :: System :: Shells - classifiers: Topic :: Terminals - classifiers: Topic :: Utilities - requires_dist: colorama ; platform_system == "Windows" - requires_dist: pytest >=6 ; extra == 'dev' - requires_dist: pytest-cov ; extra == 'dev' - requires_dist: pytest-timeout ; extra == 'dev' - requires_dist: pytest-xdist ; extra == 'dev' - requires_dist: ipywidgets >=6 ; extra == 'notebook' - requires_dist: slack-sdk ; extra == 'slack' - requires_dist: requests ; extra == 'telegram' - project_urls: homepage, https://tqdm.github.io - project_urls: repository, https://github.com/tqdm/tqdm - project_urls: changelog, https://tqdm.github.io/releases - project_urls: wiki, https://github.com/tqdm/tqdm/wiki - "###); - - let (request, _) = build_request( - &file, - &filename, - &Url::parse("https://example.org/upload").unwrap(), - &BaseClientBuilder::new().build().client(), - Some("ferris"), - Some("F3RR!S"), - &form_metadata, - Arc::new(DummyReporter), - ) - .await - .unwrap(); - - insta::with_settings!({ - filters => [("boundary=[0-9a-f-]+", "boundary=[...]")], - }, { - assert_debug_snapshot!(&request, @r###" - RequestBuilder { - inner: RequestBuilder { - method: POST, - url: Url { - scheme: "https", - cannot_be_a_base: false, - username: "", - password: None, - host: Some( - Domain( - "example.org", - ), - ), - port: None, - path: "/upload", - query: None, - fragment: None, - }, - headers: { - "content-type": "multipart/form-data; boundary=[...]", - "accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7", - "authorization": "Basic ZmVycmlzOkYzUlIhUw==", - }, - }, - .. - } - "###); - }); - } -} +mod tests; diff --git a/crates/uv-publish/src/tests.rs b/crates/uv-publish/src/tests.rs new file mode 100644 index 000000000..aac176260 --- /dev/null +++ b/crates/uv-publish/src/tests.rs @@ -0,0 +1,273 @@ +use crate::{build_request, form_metadata, Reporter}; +use insta::{assert_debug_snapshot, assert_snapshot}; +use itertools::Itertools; +use std::path::PathBuf; +use std::sync::Arc; +use url::Url; +use uv_client::BaseClientBuilder; +use uv_distribution_filename::DistFilename; + +struct DummyReporter; + +impl Reporter for DummyReporter { + fn on_progress(&self, _name: &str, _id: usize) {} + fn on_download_start(&self, _name: &str, _size: Option) -> usize { + 0 + } + fn on_download_progress(&self, _id: usize, _inc: u64) {} + fn on_download_complete(&self, _id: usize) {} +} + +/// Snapshot the data we send for an upload request for a source distribution. +#[tokio::test] +async fn upload_request_source_dist() { + let raw_filename = "tqdm-999.0.0.tar.gz"; + let file = PathBuf::from("../../scripts/links/").join(raw_filename); + let filename = DistFilename::try_from_normalized_filename(raw_filename).unwrap(); + + let form_metadata = form_metadata(&file, &filename).await.unwrap(); + + let formatted_metadata = form_metadata + .iter() + .map(|(k, v)| format!("{k}: {v}")) + .join("\n"); + assert_snapshot!(&formatted_metadata, @r###" + :action: file_upload + sha256_digest: 89fa05cffa7f457658373b85de302d24d0c205ceda2819a8739e324b75e9430b + protocol_version: 1 + metadata_version: 2.3 + name: tqdm + version: 999.0.0 + filetype: sdist + pyversion: source + description: # tqdm + + [![PyPI - Version](https://img.shields.io/pypi/v/tqdm.svg)](https://pypi.org/project/tqdm) + [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/tqdm.svg)](https://pypi.org/project/tqdm) + + ----- + + **Table of Contents** + + - [Installation](#installation) + - [License](#license) + + ## Installation + + ```console + pip install tqdm + ``` + + ## License + + `tqdm` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. + + description_content_type: text/markdown + author_email: Charlie Marsh + requires_python: >=3.8 + classifiers: Development Status :: 4 - Beta + classifiers: Programming Language :: Python + classifiers: Programming Language :: Python :: 3.8 + classifiers: Programming Language :: Python :: 3.9 + classifiers: Programming Language :: Python :: 3.10 + classifiers: Programming Language :: Python :: 3.11 + classifiers: Programming Language :: Python :: 3.12 + classifiers: Programming Language :: Python :: Implementation :: CPython + classifiers: Programming Language :: Python :: Implementation :: PyPy + project_urls: Documentation, https://github.com/unknown/tqdm#readme + project_urls: Issues, https://github.com/unknown/tqdm/issues + project_urls: Source, https://github.com/unknown/tqdm + "###); + + let (request, _) = build_request( + &file, + raw_filename, + &filename, + &Url::parse("https://example.org/upload").unwrap(), + &BaseClientBuilder::new().build().client(), + Some("ferris"), + Some("F3RR!S"), + &form_metadata, + Arc::new(DummyReporter), + ) + .await + .unwrap(); + + insta::with_settings!({ + filters => [("boundary=[0-9a-f-]+", "boundary=[...]")], + }, { + assert_debug_snapshot!(&request, @r###" + RequestBuilder { + inner: RequestBuilder { + method: POST, + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "example.org", + ), + ), + port: None, + path: "/upload", + query: None, + fragment: None, + }, + headers: { + "content-type": "multipart/form-data; boundary=[...]", + "accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7", + "authorization": "Basic ZmVycmlzOkYzUlIhUw==", + }, + }, + .. + } + "###); + }); +} + +/// Snapshot the data we send for an upload request for a wheel. +#[tokio::test] +async fn upload_request_wheel() { + let raw_filename = + "tqdm-4.66.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl"; + let file = PathBuf::from("../../scripts/links/").join(raw_filename); + let filename = DistFilename::try_from_normalized_filename(raw_filename).unwrap(); + + let form_metadata = form_metadata(&file, &filename).await.unwrap(); + + let formatted_metadata = form_metadata + .iter() + .map(|(k, v)| format!("{k}: {v}")) + .join("\n"); + assert_snapshot!(&formatted_metadata, @r###" + :action: file_upload + sha256_digest: 0d88ca657bc6b64995ca416e0c59c71af85cc10015d940fa446c42a8b485ee1c + protocol_version: 1 + metadata_version: 2.1 + name: tqdm + version: 4.66.1 + filetype: bdist_wheel + pyversion: py3 + summary: Fast, Extensible Progress Meter + description_content_type: text/x-rst + maintainer_email: tqdm developers + license: MPL-2.0 AND MIT + keywords: progressbar,progressmeter,progress,bar,meter,rate,eta,console,terminal,time + requires_python: >=3.7 + classifiers: Development Status :: 5 - Production/Stable + classifiers: Environment :: Console + classifiers: Environment :: MacOS X + classifiers: Environment :: Other Environment + classifiers: Environment :: Win32 (MS Windows) + classifiers: Environment :: X11 Applications + classifiers: Framework :: IPython + classifiers: Framework :: Jupyter + classifiers: Intended Audience :: Developers + classifiers: Intended Audience :: Education + classifiers: Intended Audience :: End Users/Desktop + classifiers: Intended Audience :: Other Audience + classifiers: Intended Audience :: System Administrators + classifiers: License :: OSI Approved :: MIT License + classifiers: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) + classifiers: Operating System :: MacOS + classifiers: Operating System :: MacOS :: MacOS X + classifiers: Operating System :: Microsoft + classifiers: Operating System :: Microsoft :: MS-DOS + classifiers: Operating System :: Microsoft :: Windows + classifiers: Operating System :: POSIX + classifiers: Operating System :: POSIX :: BSD + classifiers: Operating System :: POSIX :: BSD :: FreeBSD + classifiers: Operating System :: POSIX :: Linux + classifiers: Operating System :: POSIX :: SunOS/Solaris + classifiers: Operating System :: Unix + classifiers: Programming Language :: Python + classifiers: Programming Language :: Python :: 3 + classifiers: Programming Language :: Python :: 3.7 + classifiers: Programming Language :: Python :: 3.8 + classifiers: Programming Language :: Python :: 3.9 + classifiers: Programming Language :: Python :: 3.10 + classifiers: Programming Language :: Python :: 3.11 + classifiers: Programming Language :: Python :: 3 :: Only + classifiers: Programming Language :: Python :: Implementation + classifiers: Programming Language :: Python :: Implementation :: IronPython + classifiers: Programming Language :: Python :: Implementation :: PyPy + classifiers: Programming Language :: Unix Shell + classifiers: Topic :: Desktop Environment + classifiers: Topic :: Education :: Computer Aided Instruction (CAI) + classifiers: Topic :: Education :: Testing + classifiers: Topic :: Office/Business + classifiers: Topic :: Other/Nonlisted Topic + classifiers: Topic :: Software Development :: Build Tools + classifiers: Topic :: Software Development :: Libraries + classifiers: Topic :: Software Development :: Libraries :: Python Modules + classifiers: Topic :: Software Development :: Pre-processors + classifiers: Topic :: Software Development :: User Interfaces + classifiers: Topic :: System :: Installation/Setup + classifiers: Topic :: System :: Logging + classifiers: Topic :: System :: Monitoring + classifiers: Topic :: System :: Shells + classifiers: Topic :: Terminals + classifiers: Topic :: Utilities + requires_dist: colorama ; platform_system == "Windows" + requires_dist: pytest >=6 ; extra == 'dev' + requires_dist: pytest-cov ; extra == 'dev' + requires_dist: pytest-timeout ; extra == 'dev' + requires_dist: pytest-xdist ; extra == 'dev' + requires_dist: ipywidgets >=6 ; extra == 'notebook' + requires_dist: slack-sdk ; extra == 'slack' + requires_dist: requests ; extra == 'telegram' + project_urls: homepage, https://tqdm.github.io + project_urls: repository, https://github.com/tqdm/tqdm + project_urls: changelog, https://tqdm.github.io/releases + project_urls: wiki, https://github.com/tqdm/tqdm/wiki + "###); + + let (request, _) = build_request( + &file, + raw_filename, + &filename, + &Url::parse("https://example.org/upload").unwrap(), + &BaseClientBuilder::new().build().client(), + Some("ferris"), + Some("F3RR!S"), + &form_metadata, + Arc::new(DummyReporter), + ) + .await + .unwrap(); + + insta::with_settings!({ + filters => [("boundary=[0-9a-f-]+", "boundary=[...]")], + }, { + assert_debug_snapshot!(&request, @r###" + RequestBuilder { + inner: RequestBuilder { + method: POST, + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "example.org", + ), + ), + port: None, + path: "/upload", + query: None, + fragment: None, + }, + headers: { + "content-type": "multipart/form-data; boundary=[...]", + "accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7", + "authorization": "Basic ZmVycmlzOkYzUlIhUw==", + }, + }, + .. + } + "###); + }); +} diff --git a/crates/uv-publish/src/trusted_publishing.rs b/crates/uv-publish/src/trusted_publishing.rs index dad1b3223..98b6d9315 100644 --- a/crates/uv-publish/src/trusted_publishing.rs +++ b/crates/uv-publish/src/trusted_publishing.rs @@ -9,6 +9,7 @@ use std::fmt::Display; use thiserror::Error; use tracing::{debug, trace}; use url::Url; +use uv_static::EnvVars; #[derive(Debug, Error)] pub enum TrustedPublishingError { @@ -74,7 +75,7 @@ pub(crate) async fn get_token( client: &ClientWithMiddleware, ) -> Result { // If this fails, we can skip the audience request. - let oidc_token_request_token = env::var("ACTIONS_ID_TOKEN_REQUEST_TOKEN")?; + let oidc_token_request_token = env::var(EnvVars::ACTIONS_ID_TOKEN_REQUEST_TOKEN)?; // Request 1: Get the audience let audience = get_audience(registry, client).await?; @@ -89,7 +90,7 @@ pub(crate) async fn get_token( // Tell GitHub Actions to mask the token in any console logs. #[allow(clippy::print_stdout)] - if env::var("GITHUB_ACTIONS") == Ok("true".to_string()) { + if env::var(EnvVars::GITHUB_ACTIONS) == Ok("true".to_string()) { println!("::add-mask::{}", &publish_token); } @@ -115,7 +116,7 @@ async fn get_oidc_token( oidc_token_request_token: &str, client: &ClientWithMiddleware, ) -> Result { - let mut oidc_token_url = Url::parse(&env::var("ACTIONS_ID_TOKEN_REQUEST_URL")?)?; + let mut oidc_token_url = Url::parse(&env::var(EnvVars::ACTIONS_ID_TOKEN_REQUEST_URL)?)?; oidc_token_url .query_pairs_mut() .append_pair("audience", audience); diff --git a/crates/uv-pypi-types/Cargo.toml b/crates/uv-pypi-types/Cargo.toml index 5d2197667..a89b29d4b 100644 --- a/crates/uv-pypi-types/Cargo.toml +++ b/crates/uv-pypi-types/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-pypi-types/src/lenient_requirement.rs b/crates/uv-pypi-types/src/lenient_requirement.rs index 8baaf96ed..9dafcbce8 100644 --- a/crates/uv-pypi-types/src/lenient_requirement.rs +++ b/crates/uv-pypi-types/src/lenient_requirement.rs @@ -163,261 +163,4 @@ impl<'de> Deserialize<'de> for LenientVersionSpecifiers { } #[cfg(test)] -mod tests { - use std::str::FromStr; - - use uv_pep440::VersionSpecifiers; - use uv_pep508::Requirement; - - use crate::LenientVersionSpecifiers; - - use super::LenientRequirement; - - #[test] - fn requirement_missing_comma() { - let actual: Requirement = LenientRequirement::from_str("elasticsearch-dsl (>=7.2.0<8.0.0)") - .unwrap() - .into(); - let expected: Requirement = - Requirement::from_str("elasticsearch-dsl (>=7.2.0,<8.0.0)").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn requirement_not_equal_tile() { - let actual: Requirement = LenientRequirement::from_str("jupyter-core (!=~5.0,>=4.12)") - .unwrap() - .into(); - let expected: Requirement = Requirement::from_str("jupyter-core (!=5.0.*,>=4.12)").unwrap(); - assert_eq!(actual, expected); - - let actual: Requirement = LenientRequirement::from_str("jupyter-core (!=~5,>=4.12)") - .unwrap() - .into(); - let expected: Requirement = Requirement::from_str("jupyter-core (!=5.*,>=4.12)").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn requirement_greater_than_star() { - let actual: Requirement = LenientRequirement::from_str("torch (>=1.9.*)") - .unwrap() - .into(); - let expected: Requirement = Requirement::from_str("torch (>=1.9)").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn requirement_missing_dot() { - let actual: Requirement = - LenientRequirement::from_str("pyzmq (>=2.7,!=3.0*,!=3.1*,!=3.2*)") - .unwrap() - .into(); - let expected: Requirement = - Requirement::from_str("pyzmq (>=2.7,!=3.0.*,!=3.1.*,!=3.2.*)").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn requirement_trailing_comma() { - let actual: Requirement = LenientRequirement::from_str("pyzmq >=3.6,").unwrap().into(); - let expected: Requirement = Requirement::from_str("pyzmq >=3.6").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn specifier_missing_comma() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=7.2.0<8.0.0") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=7.2.0,<8.0.0").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn specifier_not_equal_tile() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str("!=~5.0,>=4.12") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str("!=5.0.*,>=4.12").unwrap(); - assert_eq!(actual, expected); - - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str("!=~5,>=4.12") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str("!=5.*,>=4.12").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn specifier_greater_than_star() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=1.9.*") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=1.9").unwrap(); - assert_eq!(actual, expected); - - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=1.*").unwrap().into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=1").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn specifier_missing_dot() { - let actual: VersionSpecifiers = - LenientVersionSpecifiers::from_str(">=2.7,!=3.0*,!=3.1*,!=3.2*") - .unwrap() - .into(); - let expected: VersionSpecifiers = - VersionSpecifiers::from_str(">=2.7,!=3.0.*,!=3.1.*,!=3.2.*").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn specifier_trailing_comma() { - let actual: VersionSpecifiers = - LenientVersionSpecifiers::from_str(">=3.6,").unwrap().into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=3.6").unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn specifier_trailing_comma_trailing_space() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=3.6, ") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=3.6").unwrap(); - assert_eq!(actual, expected); - } - - /// - #[test] - fn specifier_invalid_single_quotes() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">= '2.7'") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">= 2.7").unwrap(); - assert_eq!(actual, expected); - } - - /// - #[test] - fn specifier_invalid_double_quotes() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=\"3.6\"") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=3.6").unwrap(); - assert_eq!(actual, expected); - } - - /// - #[test] - fn specifier_multi_fix() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str( - ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*,", - ) - .unwrap() - .into(); - let expected: VersionSpecifiers = - VersionSpecifiers::from_str(">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*") - .unwrap(); - assert_eq!(actual, expected); - } - - /// - #[test] - fn smaller_than_star() { - let actual: VersionSpecifiers = - LenientVersionSpecifiers::from_str(">=2.7,!=3.0.*,!=3.1.*,<3.4.*") - .unwrap() - .into(); - let expected: VersionSpecifiers = - VersionSpecifiers::from_str(">=2.7,!=3.0.*,!=3.1.*,<3.4").unwrap(); - assert_eq!(actual, expected); - } - - /// - /// - #[test] - fn stray_quote() { - let actual: VersionSpecifiers = - LenientVersionSpecifiers::from_str(">=2.7, !=3.0.*, !=3.1.*', !=3.2.*, !=3.3.*'") - .unwrap() - .into(); - let expected: VersionSpecifiers = - VersionSpecifiers::from_str(">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*").unwrap(); - assert_eq!(actual, expected); - let actual: VersionSpecifiers = - LenientVersionSpecifiers::from_str(">=3.6'").unwrap().into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=3.6").unwrap(); - assert_eq!(actual, expected); - } - - /// - #[test] - fn trailing_comma_after_quote() { - let actual: Requirement = LenientRequirement::from_str("botocore>=1.3.0,<1.4.0',") - .unwrap() - .into(); - let expected: Requirement = Requirement::from_str("botocore>=1.3.0,<1.4.0").unwrap(); - assert_eq!(actual, expected); - } - - /// - #[test] - fn greater_than_dev() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">dev").unwrap().into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">0.0.0dev").unwrap(); - assert_eq!(actual, expected); - } - - /// - #[test] - fn trailing_alpha_zero() { - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=9.0.0a1.0") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=9.0.0a1").unwrap(); - assert_eq!(actual, expected); - - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=9.0a1.0") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=9.0a1").unwrap(); - assert_eq!(actual, expected); - - let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=9a1.0") - .unwrap() - .into(); - let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=9a1").unwrap(); - assert_eq!(actual, expected); - } - - /// - #[test] - fn stray_quote_preserve_marker() { - let actual: Requirement = - LenientRequirement::from_str("numpy >=1.19; python_version >= \"3.7\"") - .unwrap() - .into(); - let expected: Requirement = - Requirement::from_str("numpy >=1.19; python_version >= \"3.7\"").unwrap(); - assert_eq!(actual, expected); - - let actual: Requirement = - LenientRequirement::from_str("numpy \">=1.19\"; python_version >= \"3.7\"") - .unwrap() - .into(); - let expected: Requirement = - Requirement::from_str("numpy >=1.19; python_version >= \"3.7\"").unwrap(); - assert_eq!(actual, expected); - - let actual: Requirement = - LenientRequirement::from_str("'numpy' >=1.19\"; python_version >= \"3.7\"") - .unwrap() - .into(); - let expected: Requirement = - Requirement::from_str("numpy >=1.19; python_version >= \"3.7\"").unwrap(); - assert_eq!(actual, expected); - } -} +mod tests; diff --git a/crates/uv-pypi-types/src/lenient_requirement/tests.rs b/crates/uv-pypi-types/src/lenient_requirement/tests.rs new file mode 100644 index 000000000..64b8b58cf --- /dev/null +++ b/crates/uv-pypi-types/src/lenient_requirement/tests.rs @@ -0,0 +1,251 @@ +use std::str::FromStr; + +use uv_pep440::VersionSpecifiers; +use uv_pep508::Requirement; + +use crate::LenientVersionSpecifiers; + +use super::LenientRequirement; + +#[test] +fn requirement_missing_comma() { + let actual: Requirement = LenientRequirement::from_str("elasticsearch-dsl (>=7.2.0<8.0.0)") + .unwrap() + .into(); + let expected: Requirement = + Requirement::from_str("elasticsearch-dsl (>=7.2.0,<8.0.0)").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn requirement_not_equal_tile() { + let actual: Requirement = LenientRequirement::from_str("jupyter-core (!=~5.0,>=4.12)") + .unwrap() + .into(); + let expected: Requirement = Requirement::from_str("jupyter-core (!=5.0.*,>=4.12)").unwrap(); + assert_eq!(actual, expected); + + let actual: Requirement = LenientRequirement::from_str("jupyter-core (!=~5,>=4.12)") + .unwrap() + .into(); + let expected: Requirement = Requirement::from_str("jupyter-core (!=5.*,>=4.12)").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn requirement_greater_than_star() { + let actual: Requirement = LenientRequirement::from_str("torch (>=1.9.*)") + .unwrap() + .into(); + let expected: Requirement = Requirement::from_str("torch (>=1.9)").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn requirement_missing_dot() { + let actual: Requirement = LenientRequirement::from_str("pyzmq (>=2.7,!=3.0*,!=3.1*,!=3.2*)") + .unwrap() + .into(); + let expected: Requirement = + Requirement::from_str("pyzmq (>=2.7,!=3.0.*,!=3.1.*,!=3.2.*)").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn requirement_trailing_comma() { + let actual: Requirement = LenientRequirement::from_str("pyzmq >=3.6,").unwrap().into(); + let expected: Requirement = Requirement::from_str("pyzmq >=3.6").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn specifier_missing_comma() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=7.2.0<8.0.0") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=7.2.0,<8.0.0").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn specifier_not_equal_tile() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str("!=~5.0,>=4.12") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str("!=5.0.*,>=4.12").unwrap(); + assert_eq!(actual, expected); + + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str("!=~5,>=4.12") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str("!=5.*,>=4.12").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn specifier_greater_than_star() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=1.9.*") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=1.9").unwrap(); + assert_eq!(actual, expected); + + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=1.*").unwrap().into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=1").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn specifier_missing_dot() { + let actual: VersionSpecifiers = + LenientVersionSpecifiers::from_str(">=2.7,!=3.0*,!=3.1*,!=3.2*") + .unwrap() + .into(); + let expected: VersionSpecifiers = + VersionSpecifiers::from_str(">=2.7,!=3.0.*,!=3.1.*,!=3.2.*").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn specifier_trailing_comma() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=3.6,").unwrap().into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=3.6").unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn specifier_trailing_comma_trailing_space() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=3.6, ") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=3.6").unwrap(); + assert_eq!(actual, expected); +} + +/// +#[test] +fn specifier_invalid_single_quotes() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">= '2.7'") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">= 2.7").unwrap(); + assert_eq!(actual, expected); +} + +/// +#[test] +fn specifier_invalid_double_quotes() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=\"3.6\"") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=3.6").unwrap(); + assert_eq!(actual, expected); +} + +/// +#[test] +fn specifier_multi_fix() { + let actual: VersionSpecifiers = + LenientVersionSpecifiers::from_str(">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*,") + .unwrap() + .into(); + let expected: VersionSpecifiers = + VersionSpecifiers::from_str(">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*").unwrap(); + assert_eq!(actual, expected); +} + +/// +#[test] +fn smaller_than_star() { + let actual: VersionSpecifiers = + LenientVersionSpecifiers::from_str(">=2.7,!=3.0.*,!=3.1.*,<3.4.*") + .unwrap() + .into(); + let expected: VersionSpecifiers = + VersionSpecifiers::from_str(">=2.7,!=3.0.*,!=3.1.*,<3.4").unwrap(); + assert_eq!(actual, expected); +} + +/// +/// +#[test] +fn stray_quote() { + let actual: VersionSpecifiers = + LenientVersionSpecifiers::from_str(">=2.7, !=3.0.*, !=3.1.*', !=3.2.*, !=3.3.*'") + .unwrap() + .into(); + let expected: VersionSpecifiers = + VersionSpecifiers::from_str(">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*").unwrap(); + assert_eq!(actual, expected); + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=3.6'").unwrap().into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=3.6").unwrap(); + assert_eq!(actual, expected); +} + +/// +#[test] +fn trailing_comma_after_quote() { + let actual: Requirement = LenientRequirement::from_str("botocore>=1.3.0,<1.4.0',") + .unwrap() + .into(); + let expected: Requirement = Requirement::from_str("botocore>=1.3.0,<1.4.0").unwrap(); + assert_eq!(actual, expected); +} + +/// +#[test] +fn greater_than_dev() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">dev").unwrap().into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">0.0.0dev").unwrap(); + assert_eq!(actual, expected); +} + +/// +#[test] +fn trailing_alpha_zero() { + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=9.0.0a1.0") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=9.0.0a1").unwrap(); + assert_eq!(actual, expected); + + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=9.0a1.0") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=9.0a1").unwrap(); + assert_eq!(actual, expected); + + let actual: VersionSpecifiers = LenientVersionSpecifiers::from_str(">=9a1.0") + .unwrap() + .into(); + let expected: VersionSpecifiers = VersionSpecifiers::from_str(">=9a1").unwrap(); + assert_eq!(actual, expected); +} + +/// +#[test] +fn stray_quote_preserve_marker() { + let actual: Requirement = + LenientRequirement::from_str("numpy >=1.19; python_version >= \"3.7\"") + .unwrap() + .into(); + let expected: Requirement = + Requirement::from_str("numpy >=1.19; python_version >= \"3.7\"").unwrap(); + assert_eq!(actual, expected); + + let actual: Requirement = + LenientRequirement::from_str("numpy \">=1.19\"; python_version >= \"3.7\"") + .unwrap() + .into(); + let expected: Requirement = + Requirement::from_str("numpy >=1.19; python_version >= \"3.7\"").unwrap(); + assert_eq!(actual, expected); + + let actual: Requirement = + LenientRequirement::from_str("'numpy' >=1.19\"; python_version >= \"3.7\"") + .unwrap() + .into(); + let expected: Requirement = + Requirement::from_str("numpy >=1.19; python_version >= \"3.7\"").unwrap(); + assert_eq!(actual, expected); +} diff --git a/crates/uv-pypi-types/src/metadata/metadata23.rs b/crates/uv-pypi-types/src/metadata/metadata23.rs index d4d1d034e..8a1bfd9cc 100644 --- a/crates/uv-pypi-types/src/metadata/metadata23.rs +++ b/crates/uv-pypi-types/src/metadata/metadata23.rs @@ -2,6 +2,7 @@ use crate::metadata::Headers; use crate::MetadataError; +use std::fmt::Display; use std::str; use std::str::FromStr; @@ -9,11 +10,12 @@ use std::str::FromStr; /// . #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Metadata23 { - /// Version of the file format; legal values are `1.0`, `1.1`, `1.2`, `2.1`, `2.2` and `2.3`. + /// Version of the file format; legal values are `1.0`, `1.1`, `1.2`, `2.1`, `2.2`, `2.3` and + /// `2.4`. pub metadata_version: String, /// The name of the distribution. pub name: String, - /// A string containing the distribution’s version number. + /// A string containing the distribution's version number. pub version: String, /// A Platform specification describing an operating system supported by the distribution /// which is not listed in the “Operating System” Trove classifiers. @@ -25,55 +27,77 @@ pub struct Metadata23 { pub summary: Option, /// A longer description of the distribution that can run to several paragraphs. pub description: Option, + /// A string stating the markup syntax (if any) used in the distribution's description, + /// so that tools can intelligently render the description. + /// + /// Known values: `text/plain`, `text/markdown` and `text/x-rst`. + pub description_content_type: Option, /// A list of additional keywords, separated by commas, to be used to /// assist searching for the distribution in a larger catalog. pub keywords: Option, - /// A string containing the URL for the distribution’s home page. + /// A string containing the URL for the distribution's home page. + /// + /// Deprecated by PEP 753. pub home_page: Option, /// A string containing the URL from which this version of the distribution can be downloaded. + /// + /// Deprecated by PEP 753. pub download_url: Option, - /// A string containing the author’s name at a minimum; additional contact information may be provided. + /// A string containing the author's name at a minimum; additional contact information may be + /// provided. pub author: Option, - /// A string containing the author’s e-mail address. It can contain a name and e-mail address in the legal forms for a RFC-822 `From:` header. + /// A string containing the author's e-mail address. It can contain a name and e-mail address in + /// the legal forms for an RFC-822 `From:` header. pub author_email: Option, - /// Text indicating the license covering the distribution where the license is not a selection from the `License` Trove classifiers or an SPDX license expression. + /// A string containing the maintainer's name at a minimum; additional contact information may + /// be provided. + /// + /// Note that this field is intended for use when a project is being maintained by someone other + /// than the original author: + /// it should be omitted if it is identical to `author`. + pub maintainer: Option, + /// A string containing the maintainer's e-mail address. + /// It can contain a name and e-mail address in the legal forms for a RFC-822 `From:` header. + /// + /// Note that this field is intended for use when a project is being maintained by someone other + /// than the original author: it should be omitted if it is identical to `author_email`. + pub maintainer_email: Option, + /// Text indicating the license covering the distribution where the license is not a selection + /// from the `License` Trove classifiers or an SPDX license expression. pub license: Option, /// An SPDX expression indicating the license covering the distribution. + /// + /// Introduced by PEP 639, requires metadata version 2.4. pub license_expression: Option, /// Paths to files containing the text of the licenses covering the distribution. + /// + /// Introduced by PEP 639, requires metadata version 2.4. pub license_files: Vec, /// Each entry is a string giving a single classification value for the distribution. pub classifiers: Vec, - /// Each entry contains a string naming some other distutils project required by this distribution. + /// Each entry contains a string naming some other distutils project required by this + /// distribution. pub requires_dist: Vec, - /// Each entry contains a string naming a Distutils project which is contained within this distribution. + /// Each entry contains a string naming a Distutils project which is contained within this + /// distribution. pub provides_dist: Vec, - /// Each entry contains a string describing a distutils project’s distribution which this distribution renders obsolete, + /// Each entry contains a string describing a distutils project's distribution which this + /// distribution renders obsolete, /// meaning that the two projects should not be installed at the same time. pub obsoletes_dist: Vec, - /// A string containing the maintainer’s name at a minimum; additional contact information may be provided. - /// - /// Note that this field is intended for use when a project is being maintained by someone other than the original author: - /// it should be omitted if it is identical to `author`. - pub maintainer: Option, - /// A string containing the maintainer’s e-mail address. - /// It can contain a name and e-mail address in the legal forms for a RFC-822 `From:` header. - /// - /// Note that this field is intended for use when a project is being maintained by someone other than the original author: - /// it should be omitted if it is identical to `author_email`. - pub maintainer_email: Option, - /// This field specifies the Python version(s) that the distribution is guaranteed to be compatible with. + /// This field specifies the Python version(s) that the distribution is guaranteed to be + /// compatible with. pub requires_python: Option, - /// Each entry contains a string describing some dependency in the system that the distribution is to be used. + /// Each entry contains a string describing some dependency in the system that the distribution + /// is to be used. pub requires_external: Vec, - /// A string containing a browsable URL for the project and a label for it, separated by a comma. + /// A string containing a browsable URL for the project and a label for it, separated by a + /// comma. pub project_urls: Vec, /// A string containing the name of an optional feature. Must be a valid Python identifier. - /// May be used to make a dependency conditional on whether the optional feature has been requested. + /// May be used to make a dependency conditional on whether the optional feature has been + /// requested. pub provides_extras: Vec, - /// A string stating the markup syntax (if any) used in the distribution’s description, - /// so that tools can intelligently render the description. - pub description_content_type: Option, /// A string containing the name of another core metadata field. pub dynamic: Vec, } @@ -130,11 +154,14 @@ impl Metadata23 { supported_platforms, summary, description, + description_content_type, keywords, home_page, download_url, author, author_email, + maintainer, + maintainer_email, license, license_expression, license_files, @@ -142,16 +169,101 @@ impl Metadata23 { requires_dist, provides_dist, obsoletes_dist, - maintainer, - maintainer_email, requires_python, requires_external, project_urls, provides_extras, - description_content_type, dynamic, }) } + + /// Convert to the pseudo-email format used by Python's METADATA. + /// + /// > The standard file format for metadata (including in wheels and installed projects) is + /// > based on the format of email headers. However, email formats have been revised several + /// > times, and exactly which email RFC applies to packaging metadata is not specified. In the + /// > absence of a precise definition, the practical standard is set by what the standard + /// > library `email.parser` module can parse using the `compat32` policy. + /// - + /// + /// # Example + /// + /// ```text + /// Metadata-Version: 2.3 + /// Name: hello-world + /// Version: 0.1.0 + /// License: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + /// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A [...] + /// ``` + pub fn core_metadata_format(&self) -> String { + fn write_str(writer: &mut String, key: &str, value: impl Display) { + let value = value.to_string(); + let mut lines = value.lines(); + if let Some(line) = lines.next() { + writer.push_str(&format!("{key}: {line}\n")); + } else { + // The value is an empty string + writer.push_str(&format!("{key}: \n")); + } + for line in lines { + // Python implementations vary + // https://github.com/pypa/pyproject-metadata/pull/150/files#diff-7d938dbc255a08c2cfab1b4f1f8d1f6519c9312dd0a39d7793fa778474f1fbd1L135-R141 + writer.push_str(&format!("{}{}\n", " ".repeat(key.len() + 2), line)); + } + } + fn write_opt_str(writer: &mut String, key: &str, value: &Option) { + if let Some(value) = value { + write_str(writer, key, value); + } + } + fn write_all( + writer: &mut String, + key: &str, + values: impl IntoIterator, + ) { + for value in values { + write_str(writer, key, value); + } + } + + let mut writer = String::new(); + write_str(&mut writer, "Metadata-Version", &self.metadata_version); + write_str(&mut writer, "Name", &self.name); + write_str(&mut writer, "Version", &self.version); + write_all(&mut writer, "Platform", &self.platforms); + write_all(&mut writer, "Supported-Platform", &self.supported_platforms); + write_all(&mut writer, "Summary", &self.summary); + write_opt_str(&mut writer, "Keywords", &self.keywords); + write_opt_str(&mut writer, "Home-Page", &self.home_page); + write_opt_str(&mut writer, "Download-URL", &self.download_url); + write_opt_str(&mut writer, "Author", &self.author); + write_opt_str(&mut writer, "Author-email", &self.author_email); + write_opt_str(&mut writer, "License", &self.license); + write_opt_str(&mut writer, "License-Expression", &self.license_expression); + write_all(&mut writer, "License-File", &self.license_files); + write_all(&mut writer, "Classifier", &self.classifiers); + write_all(&mut writer, "Requires-Dist", &self.requires_dist); + write_all(&mut writer, "Provides-Dist", &self.provides_dist); + write_all(&mut writer, "Obsoletes-Dist", &self.obsoletes_dist); + write_opt_str(&mut writer, "Maintainer", &self.maintainer); + write_opt_str(&mut writer, "Maintainer-email", &self.maintainer_email); + write_opt_str(&mut writer, "Requires-Python", &self.requires_python); + write_all(&mut writer, "Requires-External", &self.requires_external); + write_all(&mut writer, "Project-URL", &self.project_urls); + write_all(&mut writer, "Provides-Extra", &self.provides_extras); + write_opt_str( + &mut writer, + "Description-Content-Type", + &self.description_content_type, + ); + write_all(&mut writer, "Dynamic", &self.dynamic); + + if let Some(description) = &self.description { + writer.push('\n'); + writer.push_str(description); + } + writer + } } impl FromStr for Metadata23 { @@ -163,37 +275,4 @@ impl FromStr for Metadata23 { } #[cfg(test)] -mod tests { - use super::*; - use crate::MetadataError; - - #[test] - fn test_parse_from_str() { - let s = "Metadata-Version: 1.0"; - let meta: Result = s.parse(); - assert!(matches!(meta, Err(MetadataError::FieldNotFound("Name")))); - - let s = "Metadata-Version: 1.0\nName: asdf"; - let meta = Metadata23::parse(s.as_bytes()); - assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); - - let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0"; - let meta = Metadata23::parse(s.as_bytes()).unwrap(); - assert_eq!(meta.metadata_version, "1.0"); - assert_eq!(meta.name, "asdf"); - assert_eq!(meta.version, "1.0"); - - let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\nDescription: a Python package"; - let meta: Metadata23 = s.parse().unwrap(); - assert_eq!(meta.description.as_deref(), Some("a Python package")); - - let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\n\na Python package"; - let meta: Metadata23 = s.parse().unwrap(); - assert_eq!(meta.description.as_deref(), Some("a Python package")); - - let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\nAuthor: 中文\n\n一个 Python 包"; - let meta: Metadata23 = s.parse().unwrap(); - assert_eq!(meta.author.as_deref(), Some("中文")); - assert_eq!(meta.description.as_deref(), Some("一个 Python 包")); - } -} +mod tests; diff --git a/crates/uv-pypi-types/src/metadata/metadata23/tests.rs b/crates/uv-pypi-types/src/metadata/metadata23/tests.rs new file mode 100644 index 000000000..be2f358cf --- /dev/null +++ b/crates/uv-pypi-types/src/metadata/metadata23/tests.rs @@ -0,0 +1,32 @@ +use super::*; +use crate::MetadataError; + +#[test] +fn test_parse_from_str() { + let s = "Metadata-Version: 1.0"; + let meta: Result = s.parse(); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Name")))); + + let s = "Metadata-Version: 1.0\nName: asdf"; + let meta = Metadata23::parse(s.as_bytes()); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); + + let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0"; + let meta = Metadata23::parse(s.as_bytes()).unwrap(); + assert_eq!(meta.metadata_version, "1.0"); + assert_eq!(meta.name, "asdf"); + assert_eq!(meta.version, "1.0"); + + let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\nDescription: a Python package"; + let meta: Metadata23 = s.parse().unwrap(); + assert_eq!(meta.description.as_deref(), Some("a Python package")); + + let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\n\na Python package"; + let meta: Metadata23 = s.parse().unwrap(); + assert_eq!(meta.description.as_deref(), Some("a Python package")); + + let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\nAuthor: 中文\n\n一个 Python 包"; + let meta: Metadata23 = s.parse().unwrap(); + assert_eq!(meta.author.as_deref(), Some("中文")); + assert_eq!(meta.description.as_deref(), Some("一个 Python 包")); +} diff --git a/crates/uv-pypi-types/src/metadata/metadata_resolver.rs b/crates/uv-pypi-types/src/metadata/metadata_resolver.rs index da68a2e46..9d854f7c7 100644 --- a/crates/uv-pypi-types/src/metadata/metadata_resolver.rs +++ b/crates/uv-pypi-types/src/metadata/metadata_resolver.rs @@ -157,73 +157,4 @@ impl ResolutionMetadata { } #[cfg(test)] -mod tests { - use super::*; - use crate::MetadataError; - use std::str::FromStr; - use uv_normalize::PackageName; - use uv_pep440::Version; - - #[test] - fn test_parse_metadata() { - let s = "Metadata-Version: 1.0"; - let meta = ResolutionMetadata::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(MetadataError::FieldNotFound("Name")))); - - let s = "Metadata-Version: 1.0\nName: asdf"; - let meta = ResolutionMetadata::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); - - let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0"; - let meta = ResolutionMetadata::parse_metadata(s.as_bytes()).unwrap(); - assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - - let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\nAuthor: 中文\n\n一个 Python 包"; - let meta = ResolutionMetadata::parse_metadata(s.as_bytes()).unwrap(); - assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - - let s = "Metadata-Version: 1.0\nName: =?utf-8?q?foobar?=\nVersion: 1.0"; - let meta = ResolutionMetadata::parse_metadata(s.as_bytes()).unwrap(); - assert_eq!(meta.name, PackageName::from_str("foobar").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - - let s = "Metadata-Version: 1.0\nName: =?utf-8?q?=C3=A4_space?= \nVersion: 1.0"; - let meta = ResolutionMetadata::parse_metadata(s.as_bytes()); - assert!(matches!(meta, Err(MetadataError::InvalidName(_)))); - } - - #[test] - fn test_parse_pkg_info() { - let s = "Metadata-Version: 2.1"; - let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()); - assert!(matches!( - meta, - Err(MetadataError::UnsupportedMetadataVersion(_)) - )); - - let s = "Metadata-Version: 2.2\nName: asdf"; - let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()); - assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); - - let s = "Metadata-Version: 2.3\nName: asdf"; - let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()); - assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); - - let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0"; - let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()).unwrap(); - assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - - let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nDynamic: Requires-Dist"; - let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()).unwrap_err(); - assert!(matches!(meta, MetadataError::DynamicField("Requires-Dist"))); - - let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nRequires-Dist: foo"; - let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()).unwrap(); - assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - assert_eq!(meta.requires_dist, vec!["foo".parse().unwrap()]); - } -} +mod tests; diff --git a/crates/uv-pypi-types/src/metadata/metadata_resolver/tests.rs b/crates/uv-pypi-types/src/metadata/metadata_resolver/tests.rs new file mode 100644 index 000000000..e4a0e90e5 --- /dev/null +++ b/crates/uv-pypi-types/src/metadata/metadata_resolver/tests.rs @@ -0,0 +1,68 @@ +use super::*; +use crate::MetadataError; +use std::str::FromStr; +use uv_normalize::PackageName; +use uv_pep440::Version; + +#[test] +fn test_parse_metadata() { + let s = "Metadata-Version: 1.0"; + let meta = ResolutionMetadata::parse_metadata(s.as_bytes()); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Name")))); + + let s = "Metadata-Version: 1.0\nName: asdf"; + let meta = ResolutionMetadata::parse_metadata(s.as_bytes()); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); + + let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0"; + let meta = ResolutionMetadata::parse_metadata(s.as_bytes()).unwrap(); + assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + + let s = "Metadata-Version: 1.0\nName: asdf\nVersion: 1.0\nAuthor: 中文\n\n一个 Python 包"; + let meta = ResolutionMetadata::parse_metadata(s.as_bytes()).unwrap(); + assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + + let s = "Metadata-Version: 1.0\nName: =?utf-8?q?foobar?=\nVersion: 1.0"; + let meta = ResolutionMetadata::parse_metadata(s.as_bytes()).unwrap(); + assert_eq!(meta.name, PackageName::from_str("foobar").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + + let s = "Metadata-Version: 1.0\nName: =?utf-8?q?=C3=A4_space?= \nVersion: 1.0"; + let meta = ResolutionMetadata::parse_metadata(s.as_bytes()); + assert!(matches!(meta, Err(MetadataError::InvalidName(_)))); +} + +#[test] +fn test_parse_pkg_info() { + let s = "Metadata-Version: 2.1"; + let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()); + assert!(matches!( + meta, + Err(MetadataError::UnsupportedMetadataVersion(_)) + )); + + let s = "Metadata-Version: 2.2\nName: asdf"; + let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); + + let s = "Metadata-Version: 2.3\nName: asdf"; + let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("Version")))); + + let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0"; + let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()).unwrap(); + assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + + let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nDynamic: Requires-Dist"; + let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()).unwrap_err(); + assert!(matches!(meta, MetadataError::DynamicField("Requires-Dist"))); + + let s = "Metadata-Version: 2.3\nName: asdf\nVersion: 1.0\nRequires-Dist: foo"; + let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()).unwrap(); + assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + assert_eq!(meta.requires_dist, vec!["foo".parse().unwrap()]); +} diff --git a/crates/uv-pypi-types/src/metadata/pyproject_toml.rs b/crates/uv-pypi-types/src/metadata/pyproject_toml.rs index 1371a3498..0cf74e9fb 100644 --- a/crates/uv-pypi-types/src/metadata/pyproject_toml.rs +++ b/crates/uv-pypi-types/src/metadata/pyproject_toml.rs @@ -225,90 +225,4 @@ impl RequiresDist { } #[cfg(test)] -mod tests { - use crate::metadata::pyproject_toml::parse_pyproject_toml; - use crate::MetadataError; - use std::str::FromStr; - use uv_normalize::PackageName; - use uv_pep440::Version; - - #[test] - fn test_parse_pyproject_toml() { - let s = r#" - [project] - name = "asdf" - "#; - let meta = parse_pyproject_toml(s); - assert!(matches!(meta, Err(MetadataError::FieldNotFound("version")))); - - let s = r#" - [project] - name = "asdf" - dynamic = ["version"] - "#; - let meta = parse_pyproject_toml(s); - assert!(matches!(meta, Err(MetadataError::DynamicField("version")))); - - let s = r#" - [project] - name = "asdf" - version = "1.0" - "#; - let meta = parse_pyproject_toml(s).unwrap(); - assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - assert!(meta.requires_python.is_none()); - assert!(meta.requires_dist.is_empty()); - assert!(meta.provides_extras.is_empty()); - - let s = r#" - [project] - name = "asdf" - version = "1.0" - requires-python = ">=3.6" - "#; - let meta = parse_pyproject_toml(s).unwrap(); - assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); - assert!(meta.requires_dist.is_empty()); - assert!(meta.provides_extras.is_empty()); - - let s = r#" - [project] - name = "asdf" - version = "1.0" - requires-python = ">=3.6" - dependencies = ["foo"] - "#; - let meta = parse_pyproject_toml(s).unwrap(); - assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); - assert_eq!(meta.requires_dist, vec!["foo".parse().unwrap()]); - assert!(meta.provides_extras.is_empty()); - - let s = r#" - [project] - name = "asdf" - version = "1.0" - requires-python = ">=3.6" - dependencies = ["foo"] - - [project.optional-dependencies] - dotenv = ["bar"] - "#; - let meta = parse_pyproject_toml(s).unwrap(); - assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); - assert_eq!(meta.version, Version::new([1, 0])); - assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); - assert_eq!( - meta.requires_dist, - vec![ - "foo".parse().unwrap(), - "bar; extra == \"dotenv\"".parse().unwrap() - ] - ); - assert_eq!(meta.provides_extras, vec!["dotenv".parse().unwrap()]); - } -} +mod tests; diff --git a/crates/uv-pypi-types/src/metadata/pyproject_toml/tests.rs b/crates/uv-pypi-types/src/metadata/pyproject_toml/tests.rs new file mode 100644 index 000000000..c137fb99b --- /dev/null +++ b/crates/uv-pypi-types/src/metadata/pyproject_toml/tests.rs @@ -0,0 +1,85 @@ +use crate::metadata::pyproject_toml::parse_pyproject_toml; +use crate::MetadataError; +use std::str::FromStr; +use uv_normalize::PackageName; +use uv_pep440::Version; + +#[test] +fn test_parse_pyproject_toml() { + let s = r#" + [project] + name = "asdf" + "#; + let meta = parse_pyproject_toml(s); + assert!(matches!(meta, Err(MetadataError::FieldNotFound("version")))); + + let s = r#" + [project] + name = "asdf" + dynamic = ["version"] + "#; + let meta = parse_pyproject_toml(s); + assert!(matches!(meta, Err(MetadataError::DynamicField("version")))); + + let s = r#" + [project] + name = "asdf" + version = "1.0" + "#; + let meta = parse_pyproject_toml(s).unwrap(); + assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + assert!(meta.requires_python.is_none()); + assert!(meta.requires_dist.is_empty()); + assert!(meta.provides_extras.is_empty()); + + let s = r#" + [project] + name = "asdf" + version = "1.0" + requires-python = ">=3.6" + "#; + let meta = parse_pyproject_toml(s).unwrap(); + assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); + assert!(meta.requires_dist.is_empty()); + assert!(meta.provides_extras.is_empty()); + + let s = r#" + [project] + name = "asdf" + version = "1.0" + requires-python = ">=3.6" + dependencies = ["foo"] + "#; + let meta = parse_pyproject_toml(s).unwrap(); + assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); + assert_eq!(meta.requires_dist, vec!["foo".parse().unwrap()]); + assert!(meta.provides_extras.is_empty()); + + let s = r#" + [project] + name = "asdf" + version = "1.0" + requires-python = ">=3.6" + dependencies = ["foo"] + + [project.optional-dependencies] + dotenv = ["bar"] + "#; + let meta = parse_pyproject_toml(s).unwrap(); + assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); + assert_eq!(meta.version, Version::new([1, 0])); + assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); + assert_eq!( + meta.requires_dist, + vec![ + "foo".parse().unwrap(), + "bar; extra == \"dotenv\"".parse().unwrap() + ] + ); + assert_eq!(meta.provides_extras, vec!["dotenv".parse().unwrap()]); +} diff --git a/crates/uv-pypi-types/src/metadata/requires_txt.rs b/crates/uv-pypi-types/src/metadata/requires_txt.rs index 083e0a9ba..fbfa4fe6d 100644 --- a/crates/uv-pypi-types/src/metadata/requires_txt.rs +++ b/crates/uv-pypi-types/src/metadata/requires_txt.rs @@ -110,61 +110,4 @@ impl RequiresTxt { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_requires_txt() { - let s = r" -Werkzeug>=0.14 -Jinja2>=2.10 - -[dev] -pytest>=3 -sphinx - -[dotenv] -python-dotenv - "; - let meta = RequiresTxt::parse(s.as_bytes()).unwrap(); - assert_eq!( - meta.requires_dist, - vec![ - "Werkzeug>=0.14".parse().unwrap(), - "Jinja2>=2.10".parse().unwrap(), - "pytest>=3; extra == \"dev\"".parse().unwrap(), - "sphinx; extra == \"dev\"".parse().unwrap(), - "python-dotenv; extra == \"dotenv\"".parse().unwrap(), - ] - ); - - let s = r" -Werkzeug>=0.14 - -[dev:] -Jinja2>=2.10 - -[:sys_platform == 'win32'] -pytest>=3 - -[] -sphinx - -[dotenv:sys_platform == 'darwin'] -python-dotenv - "; - let meta = RequiresTxt::parse(s.as_bytes()).unwrap(); - assert_eq!( - meta.requires_dist, - vec![ - "Werkzeug>=0.14".parse().unwrap(), - "Jinja2>=2.10 ; extra == \"dev\"".parse().unwrap(), - "pytest>=3; sys_platform == 'win32'".parse().unwrap(), - "sphinx".parse().unwrap(), - "python-dotenv; sys_platform == 'darwin' and extra == \"dotenv\"" - .parse() - .unwrap(), - ] - ); - } -} +mod tests; diff --git a/crates/uv-pypi-types/src/metadata/requires_txt/tests.rs b/crates/uv-pypi-types/src/metadata/requires_txt/tests.rs new file mode 100644 index 000000000..ee20fd62e --- /dev/null +++ b/crates/uv-pypi-types/src/metadata/requires_txt/tests.rs @@ -0,0 +1,56 @@ +use super::*; + +#[test] +fn test_requires_txt() { + let s = r" +Werkzeug>=0.14 +Jinja2>=2.10 + +[dev] +pytest>=3 +sphinx + +[dotenv] +python-dotenv + "; + let meta = RequiresTxt::parse(s.as_bytes()).unwrap(); + assert_eq!( + meta.requires_dist, + vec![ + "Werkzeug>=0.14".parse().unwrap(), + "Jinja2>=2.10".parse().unwrap(), + "pytest>=3; extra == \"dev\"".parse().unwrap(), + "sphinx; extra == \"dev\"".parse().unwrap(), + "python-dotenv; extra == \"dotenv\"".parse().unwrap(), + ] + ); + + let s = r" +Werkzeug>=0.14 + +[dev:] +Jinja2>=2.10 + +[:sys_platform == 'win32'] +pytest>=3 + +[] +sphinx + +[dotenv:sys_platform == 'darwin'] +python-dotenv + "; + let meta = RequiresTxt::parse(s.as_bytes()).unwrap(); + assert_eq!( + meta.requires_dist, + vec![ + "Werkzeug>=0.14".parse().unwrap(), + "Jinja2>=2.10 ; extra == \"dev\"".parse().unwrap(), + "pytest>=3; sys_platform == 'win32'".parse().unwrap(), + "sphinx".parse().unwrap(), + "python-dotenv; sys_platform == 'darwin' and extra == \"dotenv\"" + .parse() + .unwrap(), + ] + ); +} diff --git a/crates/uv-pypi-types/src/parsed_url.rs b/crates/uv-pypi-types/src/parsed_url.rs index ecc1031b5..3afcadd70 100644 --- a/crates/uv-pypi-types/src/parsed_url.rs +++ b/crates/uv-pypi-types/src/parsed_url.rs @@ -507,46 +507,4 @@ impl From for Url { } #[cfg(test)] -mod tests { - use anyhow::Result; - use url::Url; - - use crate::parsed_url::ParsedUrl; - - #[test] - fn direct_url_from_url() -> Result<()> { - let expected = Url::parse("git+https://github.com/pallets/flask.git")?; - let actual = Url::from(ParsedUrl::try_from(expected.clone())?); - assert_eq!(expected, actual); - - let expected = Url::parse("git+https://github.com/pallets/flask.git#subdirectory=pkg_dir")?; - let actual = Url::from(ParsedUrl::try_from(expected.clone())?); - assert_eq!(expected, actual); - - let expected = Url::parse("git+https://github.com/pallets/flask.git@2.0.0")?; - let actual = Url::from(ParsedUrl::try_from(expected.clone())?); - assert_eq!(expected, actual); - - let expected = - Url::parse("git+https://github.com/pallets/flask.git@2.0.0#subdirectory=pkg_dir")?; - let actual = Url::from(ParsedUrl::try_from(expected.clone())?); - assert_eq!(expected, actual); - - // TODO(charlie): Preserve other fragments. - let expected = - Url::parse("git+https://github.com/pallets/flask.git#egg=flask&subdirectory=pkg_dir")?; - let actual = Url::from(ParsedUrl::try_from(expected.clone())?); - assert_ne!(expected, actual); - - Ok(()) - } - - #[test] - #[cfg(unix)] - fn direct_url_from_url_absolute() -> Result<()> { - let expected = Url::parse("file:///path/to/directory")?; - let actual = Url::from(ParsedUrl::try_from(expected.clone())?); - assert_eq!(expected, actual); - Ok(()) - } -} +mod tests; diff --git a/crates/uv-pypi-types/src/parsed_url/tests.rs b/crates/uv-pypi-types/src/parsed_url/tests.rs new file mode 100644 index 000000000..b5f606bbc --- /dev/null +++ b/crates/uv-pypi-types/src/parsed_url/tests.rs @@ -0,0 +1,41 @@ +use anyhow::Result; +use url::Url; + +use crate::parsed_url::ParsedUrl; + +#[test] +fn direct_url_from_url() -> Result<()> { + let expected = Url::parse("git+https://github.com/pallets/flask.git")?; + let actual = Url::from(ParsedUrl::try_from(expected.clone())?); + assert_eq!(expected, actual); + + let expected = Url::parse("git+https://github.com/pallets/flask.git#subdirectory=pkg_dir")?; + let actual = Url::from(ParsedUrl::try_from(expected.clone())?); + assert_eq!(expected, actual); + + let expected = Url::parse("git+https://github.com/pallets/flask.git@2.0.0")?; + let actual = Url::from(ParsedUrl::try_from(expected.clone())?); + assert_eq!(expected, actual); + + let expected = + Url::parse("git+https://github.com/pallets/flask.git@2.0.0#subdirectory=pkg_dir")?; + let actual = Url::from(ParsedUrl::try_from(expected.clone())?); + assert_eq!(expected, actual); + + // TODO(charlie): Preserve other fragments. + let expected = + Url::parse("git+https://github.com/pallets/flask.git#egg=flask&subdirectory=pkg_dir")?; + let actual = Url::from(ParsedUrl::try_from(expected.clone())?); + assert_ne!(expected, actual); + + Ok(()) +} + +#[test] +#[cfg(unix)] +fn direct_url_from_url_absolute() -> Result<()> { + let expected = Url::parse("file:///path/to/directory")?; + let actual = Url::from(ParsedUrl::try_from(expected.clone())?); + assert_eq!(expected, actual); + Ok(()) +} diff --git a/crates/uv-pypi-types/src/requirement.rs b/crates/uv-pypi-types/src/requirement.rs index 499b77411..101b943f3 100644 --- a/crates/uv-pypi-types/src/requirement.rs +++ b/crates/uv-pypi-types/src/requirement.rs @@ -318,8 +318,8 @@ pub enum RequirementSource { /// The requirement has a version specifier, such as `foo >1,<2`. Registry { specifier: VersionSpecifiers, - /// Choose a version from the index with this name. - index: Option, + /// Choose a version from the index at the given URL. + index: Option, }, // TODO(konsti): Track and verify version specifier from `project.dependencies` matches the // version in remote location. @@ -607,7 +607,7 @@ enum RequirementSourceWire { Registry { #[serde(skip_serializing_if = "VersionSpecifiers::is_empty", default)] specifier: VersionSpecifiers, - index: Option, + index: Option, }, } @@ -827,50 +827,4 @@ pub fn redact_git_credentials(url: &mut Url) { } #[cfg(test)] -mod tests { - use std::path::PathBuf; - - use uv_pep508::{MarkerTree, VerbatimUrl}; - - use crate::{Requirement, RequirementSource}; - - #[test] - fn roundtrip() { - let requirement = Requirement { - name: "foo".parse().unwrap(), - extras: vec![], - marker: MarkerTree::TRUE, - source: RequirementSource::Registry { - specifier: ">1,<2".parse().unwrap(), - index: None, - }, - origin: None, - }; - - let raw = toml::to_string(&requirement).unwrap(); - let deserialized: Requirement = toml::from_str(&raw).unwrap(); - assert_eq!(requirement, deserialized); - - let path = if cfg!(windows) { - "C:\\home\\ferris\\foo" - } else { - "/home/ferris/foo" - }; - let requirement = Requirement { - name: "foo".parse().unwrap(), - extras: vec![], - marker: MarkerTree::TRUE, - source: RequirementSource::Directory { - install_path: PathBuf::from(path), - editable: false, - r#virtual: false, - url: VerbatimUrl::from_absolute_path(path).unwrap(), - }, - origin: None, - }; - - let raw = toml::to_string(&requirement).unwrap(); - let deserialized: Requirement = toml::from_str(&raw).unwrap(); - assert_eq!(requirement, deserialized); - } -} +mod tests; diff --git a/crates/uv-pypi-types/src/requirement/tests.rs b/crates/uv-pypi-types/src/requirement/tests.rs new file mode 100644 index 000000000..24aa27aa3 --- /dev/null +++ b/crates/uv-pypi-types/src/requirement/tests.rs @@ -0,0 +1,45 @@ +use std::path::PathBuf; + +use uv_pep508::{MarkerTree, VerbatimUrl}; + +use crate::{Requirement, RequirementSource}; + +#[test] +fn roundtrip() { + let requirement = Requirement { + name: "foo".parse().unwrap(), + extras: vec![], + marker: MarkerTree::TRUE, + source: RequirementSource::Registry { + specifier: ">1,<2".parse().unwrap(), + index: None, + }, + origin: None, + }; + + let raw = toml::to_string(&requirement).unwrap(); + let deserialized: Requirement = toml::from_str(&raw).unwrap(); + assert_eq!(requirement, deserialized); + + let path = if cfg!(windows) { + "C:\\home\\ferris\\foo" + } else { + "/home/ferris/foo" + }; + let requirement = Requirement { + name: "foo".parse().unwrap(), + extras: vec![], + marker: MarkerTree::TRUE, + source: RequirementSource::Directory { + install_path: PathBuf::from(path), + editable: false, + r#virtual: false, + url: VerbatimUrl::from_absolute_path(path).unwrap(), + }, + origin: None, + }; + + let raw = toml::to_string(&requirement).unwrap(); + let deserialized: Requirement = toml::from_str(&raw).unwrap(); + assert_eq!(requirement, deserialized); +} diff --git a/crates/uv-pypi-types/src/simple_json.rs b/crates/uv-pypi-types/src/simple_json.rs index 5d6868e8d..efba207de 100644 --- a/crates/uv-pypi-types/src/simple_json.rs +++ b/crates/uv-pypi-types/src/simple_json.rs @@ -425,75 +425,4 @@ pub enum HashError { } #[cfg(test)] -mod tests { - use crate::{HashError, Hashes}; - - #[test] - fn parse_hashes() -> Result<(), HashError> { - let hashes: Hashes = - "sha512:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?; - assert_eq!( - hashes, - Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: Some( - "40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into() - ), - } - ); - - let hashes: Hashes = - "sha384:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?; - assert_eq!( - hashes, - Hashes { - md5: None, - sha256: None, - sha384: Some( - "40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into() - ), - sha512: None - } - ); - - let hashes: Hashes = - "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?; - assert_eq!( - hashes, - Hashes { - md5: None, - sha256: Some( - "40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into() - ), - sha384: None, - sha512: None - } - ); - - let hashes: Hashes = - "md5:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2".parse()?; - assert_eq!( - hashes, - Hashes { - md5: Some( - "090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2".into() - ), - sha256: None, - sha384: None, - sha512: None - } - ); - - let result = "sha256=40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f" - .parse::(); - assert!(result.is_err()); - - let result = "blake2:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619" - .parse::(); - assert!(result.is_err()); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-pypi-types/src/simple_json/tests.rs b/crates/uv-pypi-types/src/simple_json/tests.rs new file mode 100644 index 000000000..cbf94b52b --- /dev/null +++ b/crates/uv-pypi-types/src/simple_json/tests.rs @@ -0,0 +1,62 @@ +use crate::{HashError, Hashes}; + +#[test] +fn parse_hashes() -> Result<(), HashError> { + let hashes: Hashes = + "sha512:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?; + assert_eq!( + hashes, + Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: Some("40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()), + } + ); + + let hashes: Hashes = + "sha384:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?; + assert_eq!( + hashes, + Hashes { + md5: None, + sha256: None, + sha384: Some("40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()), + sha512: None + } + ); + + let hashes: Hashes = + "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?; + assert_eq!( + hashes, + Hashes { + md5: None, + sha256: Some("40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()), + sha384: None, + sha512: None + } + ); + + let hashes: Hashes = + "md5:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2".parse()?; + assert_eq!( + hashes, + Hashes { + md5: Some("090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2".into()), + sha256: None, + sha384: None, + sha512: None + } + ); + + let result = + "sha256=40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse::(); + assert!(result.is_err()); + + let result = + "blake2:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619".parse::(); + assert!(result.is_err()); + + Ok(()) +} diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml index 190d8d322..361d3fa7d 100644 --- a/crates/uv-python/Cargo.toml +++ b/crates/uv-python/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -26,6 +29,7 @@ uv-pep508 = { workspace = true } uv-platform-tags = { workspace = true } uv-pypi-types = { workspace = true } uv-state = { workspace = true } +uv-static = { workspace = true } uv-warnings = { workspace = true } anyhow = { workspace = true } @@ -53,12 +57,8 @@ tracing = { workspace = true } url = { workspace = true } which = { workspace = true } -[target.'cfg(any(unix, target_os = "wasi", target_os = "redox"))'.dependencies] -rustix = { workspace = true } - [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true } -winsafe = { workspace = true } windows-registry = { workspace = true } windows-result = { workspace = true } diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index 53f3fcbbc..7798d5a1b 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -1,4 +1,368 @@ { + "cpython-3.13.0-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "e94fafbac07da52c965cb6a7ffc51ce779bd253cd98af801347aac791b96499f", + "variant": null + }, + "cpython-3.13.0-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "406664681bd44af35756ad08f5304f1ec57070bb76fae8ff357ff177f229b224", + "variant": null + }, + "cpython-3.13.0-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "06e633164cb0133685a2ce14af88df0dbcaea4b0b2c5d3348d6b81393307481a", + "variant": null + }, + "cpython-3.13.0-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "1b18f0eac4c3578ecca52ff388276546c701cea22410235716195c52ad7d0344", + "variant": null + }, + "cpython-3.13.0-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "be2bbcb985ecf12eb7a16c18043a2b0b8551d8e8799c49a0d766b541dd465f47", + "variant": null + }, + "cpython-3.13.0-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "afe014200fea7505a67658fd82e70ccb49982deee752809849e781b941b941ec", + "variant": null + }, + "cpython-3.13.0-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b5782c027a8802b19656e961f73193cf060b124fd052dff19bb6d21b9e51ed14", + "variant": null + }, + "cpython-3.13.0-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b5e74d1e16402b633c6f04519618231fc0dbae7d2f9e4b1ac17c294cc3d3d076", + "variant": null + }, + "cpython-3.13.0-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "10978500ab6589760716c644aeadffa0f2c0bf31ea10f0c6160fee933933a567", + "variant": null + }, + "cpython-3.13.0-windows-i686-none": { + "name": "cpython", + "arch": "i686", + "os": "windows", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d5538ed2a247220516d4c14e8452f2c49318b29f8b524c908a1ed42e405bd8cc", + "variant": null + }, + "cpython-3.13.0-windows-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "windows", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "c8134287496727922a5c47896b4f2b1623e3aab91cbb7c1ca64542db7593f3f1", + "variant": null + }, + "cpython-3.13.0+freethreaded-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "efc2e71c0e05bc5bedb7a846e05f28dd26491b1744ded35ed82f8b49ccfa684b", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "2e07dfea62fe2215738551a179c87dbed1cc79d1b3654f4d7559889a6d5ce4eb", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-unknown-linux-gnu-freethreaded%2Bdebug-full.tar.zst", + "sha256": "3b2f53f544d1cb81520bd45446b68f7d51b5bd65e6a2a1422450fd9472c18e6c", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabi-freethreaded%2Bdebug-full.tar.zst", + "sha256": "316cf6a946150ac6b42a1c43f18977966682b9d153c5891f717fba92f0cbe06f", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabihf-freethreaded%2Bdebug-full.tar.zst", + "sha256": "0b92de0f95fb304991749e5a7beb6778f961fbf0ce5057cc6e34d6754b0a0137", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-ppc64le-unknown-linux-gnu-freethreaded%2Bdebug-full.tar.zst", + "sha256": "2adcf07d6dea3a83747e24cd70721a15fd2b10a31e78314bd6782d3992b1670d", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-s390x-unknown-linux-gnu-freethreaded%2Bdebug-full.tar.zst", + "sha256": "3b45d2be68ac66dde2d6cae55156806844f337063f475587c9fa023eea24460d", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "a73adeda301ad843cce05f31a2d3e76222b656984535a7b87696a24a098b216c", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-windows-i686-none": { + "name": "cpython", + "arch": "i686", + "os": "windows", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "7794b0209af46b6347aab945f1ccc3b24add0a17b3f6fb7741447bc44d10bf4a", + "variant": "freethreaded" + }, + "cpython-3.13.0+freethreaded-windows-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "windows", + "libc": "none", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "bfd89f9acf866463bc4baf01733da5e767d13f5d0112175a4f57ba91f1541310", + "variant": "freethreaded" + }, + "cpython-3.13.0+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "16a18678b2b524e183050e719cadaf4f207572f940bc9d6a93110a11beae80c3", + "variant": "debug" + }, + "cpython-3.13.0+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "5fa5b6bf29c149b3a3530c3dc1a7e28b0038ed1f1ea9e89e6e1446b9b0f578af", + "variant": "debug" + }, + "cpython-3.13.0+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "eef0e257456ab8f52b1085de4ebbacb4602c2bc9d2f8788dae6218ad1a9bf89c", + "variant": "debug" + }, + "cpython-3.13.0+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d1ca79a35e018974413ceca143ce081b6fff8b0ade2ceb557867ea644f9d89df", + "variant": "debug" + }, + "cpython-3.13.0+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4b53d948dc8c747ed65f92cf7d89ac5a90cb4c6e46225124d7ea46b8f275597e", + "variant": "debug" + }, + "cpython-3.13.0+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "cb96109e25c85e202a14aa6034a09bb474e4a5237a2b46e732300c93e0f443cc", + "variant": "debug" + }, + "cpython-3.13.0+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2647425970b209fc546b0ff94d25567db5575847a8a852a0d79445a3c3806c85", + "variant": "debug" + }, "cpython-3.13.0rc3-darwin-aarch64-none": { "name": "cpython", "arch": "aarch64", @@ -9,7 +373,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "685ef71882f16eabab0bc838094727978370f0ad95c29f7f5c244ffa31316aeb" + "sha256": "685ef71882f16eabab0bc838094727978370f0ad95c29f7f5c244ffa31316aeb", + "variant": null }, "cpython-3.13.0rc3-darwin-x86_64-none": { "name": "cpython", @@ -21,7 +386,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "0f5f9fcf82093c428b80c552165544439f4adcdbe5129ecf721d619e532e9b5e" + "sha256": "0f5f9fcf82093c428b80c552165544439f4adcdbe5129ecf721d619e532e9b5e", + "variant": null }, "cpython-3.13.0rc3-linux-aarch64-gnu": { "name": "cpython", @@ -33,7 +399,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1414c6b37f37e8fd9d14e48d81e313eb9c965cb0330747d5d2d689dd7e0c7043" + "sha256": "1414c6b37f37e8fd9d14e48d81e313eb9c965cb0330747d5d2d689dd7e0c7043", + "variant": null }, "cpython-3.13.0rc3-linux-armv7-gnueabi": { "name": "cpython", @@ -45,7 +412,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "11befeaf4768c2ebbb258f5b07f94b7700f16424f858d6d2c250b434e99ce07c" + "sha256": "11befeaf4768c2ebbb258f5b07f94b7700f16424f858d6d2c250b434e99ce07c", + "variant": null }, "cpython-3.13.0rc3-linux-armv7-gnueabihf": { "name": "cpython", @@ -57,7 +425,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "b7180d5ea5fda2f397d04e2e6e11a2a7e0d732542bf54c484afb81d087a7b927" + "sha256": "b7180d5ea5fda2f397d04e2e6e11a2a7e0d732542bf54c484afb81d087a7b927", + "variant": null }, "cpython-3.13.0rc3-linux-powerpc64le-gnu": { "name": "cpython", @@ -69,7 +438,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "59a2a81991d78bd658742d69b577a2b4c0734628ed42bff68615686eaf96f2ab" + "sha256": "59a2a81991d78bd658742d69b577a2b4c0734628ed42bff68615686eaf96f2ab", + "variant": null }, "cpython-3.13.0rc3-linux-s390x-gnu": { "name": "cpython", @@ -81,7 +451,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2769182e58b0dddec15222bfeecbd4b12fde61c38f23a90aa942514f3545fb9b" + "sha256": "2769182e58b0dddec15222bfeecbd4b12fde61c38f23a90aa942514f3545fb9b", + "variant": null }, "cpython-3.13.0rc3-linux-x86_64-gnu": { "name": "cpython", @@ -93,7 +464,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "445156c61e1cc167f7b8777ad08cc36e5598e12cd27e07453f6e6dc0f62e421e" + "sha256": "445156c61e1cc167f7b8777ad08cc36e5598e12cd27e07453f6e6dc0f62e421e", + "variant": null }, "cpython-3.13.0rc3-linux-x86_64-musl": { "name": "cpython", @@ -105,7 +477,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "4df6b7665c735a728d72e6f49034f1a6b7d9a54b0fbc472dc2ca525eb3dd513f" + "sha256": "4df6b7665c735a728d72e6f49034f1a6b7d9a54b0fbc472dc2ca525eb3dd513f", + "variant": null }, "cpython-3.13.0rc3-windows-i686-none": { "name": "cpython", @@ -117,7 +490,8 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "873905b3e5e8cba700126e8d6ed28ad3aef0dd102f730f8ca196018477dd2da6" + "sha256": "873905b3e5e8cba700126e8d6ed28ad3aef0dd102f730f8ca196018477dd2da6", + "variant": null }, "cpython-3.13.0rc3-windows-x86_64-none": { "name": "cpython", @@ -129,7 +503,99 @@ "patch": 0, "prerelease": "rc3", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "b59317828ef88f138ee122d420b60f2705bc72ae846ff69562e79e6c5cbc3177" + "sha256": "b59317828ef88f138ee122d420b60f2705bc72ae846ff69562e79e6c5cbc3177", + "variant": null + }, + "cpython-3.13.0rc3+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc3", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "84ca46dcb5057453373ba8d7129d9998769194c8110c81ac97a99ec1160abf41", + "variant": "debug" + }, + "cpython-3.13.0rc3+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc3", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "a2416da5fdb5331d84b179ed047245b6379c04d1c57e3c8583fda84a31dd5979", + "variant": "debug" + }, + "cpython-3.13.0rc3+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc3", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "59af55b12d59f5fdc236ba40aebb105fc440c36effadcfa7199362b2ca09d0a5", + "variant": "debug" + }, + "cpython-3.13.0rc3+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc3", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3c2808375869079e47923368903501913f32c65dfe71fe43c9ebbcdfc13009d9", + "variant": "debug" + }, + "cpython-3.13.0rc3+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc3", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "660e31ef1a7b4358332ef419e639d09a025a0e222855e10d93ee884a7fa8f15e", + "variant": "debug" + }, + "cpython-3.13.0rc3+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc3", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "92a80f38919a852edcff68fd489152a408e43c65e67b800c02801cc58f239b95", + "variant": "debug" + }, + "cpython-3.13.0rc3+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc3", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "59b19a2ae830bd67bc8190bd839ebdf2423e871ef2e5114f38b84dab652c2e1b", + "variant": "debug" }, "cpython-3.13.0rc2-darwin-aarch64-none": { "name": "cpython", @@ -141,7 +607,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "9e17f9fcc314a5dd489089a7502a525c4dd08af862f9cf33b52161a752f2a5b7" + "sha256": "9e17f9fcc314a5dd489089a7502a525c4dd08af862f9cf33b52161a752f2a5b7", + "variant": null }, "cpython-3.13.0rc2-darwin-x86_64-none": { "name": "cpython", @@ -153,7 +620,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "971668ac7f3168efc4d2b589e9d36247ab8ca9f9525c56c8aa7bfd374060105b" + "sha256": "971668ac7f3168efc4d2b589e9d36247ab8ca9f9525c56c8aa7bfd374060105b", + "variant": null }, "cpython-3.13.0rc2-linux-aarch64-gnu": { "name": "cpython", @@ -165,7 +633,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "d99a663d3b9f8792a659e366372e685550045cad12aef11645c06a9b6edcd071" + "sha256": "d99a663d3b9f8792a659e366372e685550045cad12aef11645c06a9b6edcd071", + "variant": null }, "cpython-3.13.0rc2-linux-armv7-gnueabi": { "name": "cpython", @@ -177,7 +646,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "4ca7f2aeaabf8dbb2193f0fa86f869525a5c209eb403a39a73f4cf7040cf3613" + "sha256": "4ca7f2aeaabf8dbb2193f0fa86f869525a5c209eb403a39a73f4cf7040cf3613", + "variant": null }, "cpython-3.13.0rc2-linux-armv7-gnueabihf": { "name": "cpython", @@ -189,7 +659,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "0db2d263bdbb3af1e8dc0677fa44a5cda992ba989551346ccbbfd50a86135c3d" + "sha256": "0db2d263bdbb3af1e8dc0677fa44a5cda992ba989551346ccbbfd50a86135c3d", + "variant": null }, "cpython-3.13.0rc2-linux-powerpc64le-gnu": { "name": "cpython", @@ -201,7 +672,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "70073333f7d3f0b900c7299659fec069bbefd5e04808b3729d2434b2232ac729" + "sha256": "70073333f7d3f0b900c7299659fec069bbefd5e04808b3729d2434b2232ac729", + "variant": null }, "cpython-3.13.0rc2-linux-s390x-gnu": { "name": "cpython", @@ -213,7 +685,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "50a2080e30d1504e76e5471e46830f0b4974c66b538ed8ec7df416975133ff89" + "sha256": "50a2080e30d1504e76e5471e46830f0b4974c66b538ed8ec7df416975133ff89", + "variant": null }, "cpython-3.13.0rc2-linux-x86_64-gnu": { "name": "cpython", @@ -225,7 +698,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "1893a218709d3664b7a2b80f5598b5f25c0c3fe2bcc8d0a1c75eec6bbb93d602" + "sha256": "1893a218709d3664b7a2b80f5598b5f25c0c3fe2bcc8d0a1c75eec6bbb93d602", + "variant": null }, "cpython-3.13.0rc2-linux-x86_64-musl": { "name": "cpython", @@ -237,7 +711,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "6f09aa5ba6aab8bf21955dbc3d6bab19125130ef0ebe29242b0e5ac1eebb3161" + "sha256": "6f09aa5ba6aab8bf21955dbc3d6bab19125130ef0ebe29242b0e5ac1eebb3161", + "variant": null }, "cpython-3.13.0rc2-windows-i686-none": { "name": "cpython", @@ -249,7 +724,8 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "759f600b27a6a0ef2638cb02e8bbcc6de726dd1c896759f78da3e412f6c992e9" + "sha256": "759f600b27a6a0ef2638cb02e8bbcc6de726dd1c896759f78da3e412f6c992e9", + "variant": null }, "cpython-3.13.0rc2-windows-x86_64-none": { "name": "cpython", @@ -261,7 +737,99 @@ "patch": 0, "prerelease": "rc2", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "c883205751c714bd0519592673a88f160a55d34344cc1368353ad34a679eb94a" + "sha256": "c883205751c714bd0519592673a88f160a55d34344cc1368353ad34a679eb94a", + "variant": null + }, + "cpython-3.13.0rc2+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc2", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "77005f4de8eab59d5323bf4c8236530f477b2585b92ffe6b533a1de15df3f9b2", + "variant": "debug" + }, + "cpython-3.13.0rc2+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc2", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "50110dd0a39e663394d0a0757753714efb853ee1a6fbf969bb4bbe159f6a3f83", + "variant": "debug" + }, + "cpython-3.13.0rc2+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc2", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "8a8c2d371ab7fe2d1d9f51717ad51821108c607f7ea7993f96920666afc51ac1", + "variant": "debug" + }, + "cpython-3.13.0rc2+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc2", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d1e56f2c54775edd51ef933cd2838f692c57e24e43c4bd1a1b11c86056a24ef4", + "variant": "debug" + }, + "cpython-3.13.0rc2+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc2", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "65ea35a96bce6d097ebbbf19ad484f0101b2b42fcca3ab518109c3ea3aefb952", + "variant": "debug" + }, + "cpython-3.13.0rc2+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc2", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8595be42ea7fa43ffe66761c713ad4b60e6270dca1771491d54e8d6556bb617b", + "variant": "debug" + }, + "cpython-3.13.0rc2+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 13, + "patch": 0, + "prerelease": "rc2", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "634e538c9d9e8cec2f27aa278a1e99d6e652d7b013b4f27a0242265e0d8ad0ff", + "variant": "debug" }, "cpython-3.12.7-darwin-aarch64-none": { "name": "cpython", @@ -272,8 +840,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "473c8745464e2b20477bb4faa2e0de51b6fadae68ecd79ca7951df2d15c1b214" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "95dd397e3aef4cc1846867cf20be704bdd74edd16ea8032caf01e48f0c53d65d", + "variant": null }, "cpython-3.12.7-darwin-x86_64-none": { "name": "cpython", @@ -284,8 +853,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "acc9294db71a23be6ca947b039b1fc5be40638d2ab212034dba37abe4df9f53b" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "848405b92bda20fad1f9bba99234c7d3f11e0b31e46f89835d1cb3d735e932aa", + "variant": null }, "cpython-3.12.7-linux-aarch64-gnu": { "name": "cpython", @@ -296,8 +866,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5d2cf6bfd11351ba6d07a112ff563a62176a3f9f90a169f350f8c4d7489c08d5" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c8f5ed70ee3c19da72d117f7b306adc6ca1eaf26afcbe1cc1be57d1e18df184c", + "variant": null }, "cpython-3.12.7-linux-armv7-gnueabi": { "name": "cpython", @@ -308,8 +879,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "92a84ee668037716bafad6e2872ee568c8480f45d5c5711b3a55c2a2e78bc5c5" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "d73cb8428a105d01141dee0ceec445328ab70e039e31cd8c5c1d7d226fb67afc", + "variant": null }, "cpython-3.12.7-linux-armv7-gnueabihf": { "name": "cpython", @@ -320,8 +892,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "8d5e9053d89932734e77c312ba60f100413a19d54d377927424aebb0b0bfbad8" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "04b3087272d2bb8df98eec5fe81b666052907f292381cbecce17bec40fdd30c5", + "variant": null }, "cpython-3.12.7-linux-powerpc64le-gnu": { "name": "cpython", @@ -332,8 +905,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a8108eb85e3c33fd1bfb4ecee17cf8670b344e0dc54b93d7dcf2d31b7deecd46" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "922aa21fb9eacdd1c0a26ced4dca2725595453ae5b922d56b39ebdd2388175fd", + "variant": null }, "cpython-3.12.7-linux-s390x-gnu": { "name": "cpython", @@ -344,8 +918,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "dde181a2d7bcad9239b47d2e416f2fa0b0022052955227ab980ef6ca84a400c4" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8e92d65b245b572fa6f520d428a9807a9da36428c7379a11d41ae428e69ed921", + "variant": null }, "cpython-3.12.7-linux-x86_64-gnu": { "name": "cpython", @@ -356,8 +931,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5ca4e88da44d5e07462a4a8b5ad27956dad9eedcfae4b1db37a784daf1827396" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3a4d53a7ba3916c0c1f35cbbe57068e2571b138389f29cf5c35367fec8f4c617", + "variant": null }, "cpython-3.12.7-linux-x86_64-musl": { "name": "cpython", @@ -368,8 +944,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a531873f3c382520e9f773ac3c84227e46ceb482b35a3d8703b4fab229096315" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "9314cb4d5aa525f2dc9f8d6ac204bebcfdfa8eb0dd4d3788af68769184355484", + "variant": null }, "cpython-3.12.7-windows-i686-none": { "name": "cpython", @@ -380,8 +957,9 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "e55a74a174295a5fc32b0c3da07ef100bf27a99b381ed4e60f8bf981459eb63f" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d7d7c897f11f12808d3fd9a0ce48e4de19369df4a9ee9390a4adae302902e333", + "variant": null }, "cpython-3.12.7-windows-x86_64-none": { "name": "cpython", @@ -392,8 +970,100 @@ "minor": 12, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "cf4d81ba8e8b34c18d30d2de747c472b3d7a339063e71f2e6c910798469c3252" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "fa8ac308a7cd1774d599ad9a29f1e374fbdc11453b12a8c50cc4afdb5c4bfd1a", + "variant": null + }, + "cpython-3.12.7+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0dcb036ae5205b6298c98df0ebe8281194a69b5b33e062f683959875c9a596ae", + "variant": "debug" + }, + "cpython-3.12.7+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 12, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "56cf552056642a670188282805b686587d3107eb12a9cffba8444527fda9c895", + "variant": "debug" + }, + "cpython-3.12.7+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 12, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "d8d570d9f75c4b6c5e7ca93dea7ebf85473fad356b0dc4a3e0f8d499e19d359d", + "variant": "debug" + }, + "cpython-3.12.7+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "759b21d6175cc10c56bccecfd69c79ba25449b78c0ab11a2518ce8063a9beb74", + "variant": "debug" + }, + "cpython-3.12.7+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fa8d2853e302fc99762d01f07a8a9933c3951b5404246a667c9d8eee2661661a", + "variant": "debug" + }, + "cpython-3.12.7+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fcc678bdb212c2f33d67b9de1caed7a2ed6439d271de8a1e765dda4d3d7a638b", + "variant": "debug" + }, + "cpython-3.12.7+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5c73361c6bede4dbe8de2bf81fd3006451a7941f547e5474141c3fcb400d648e", + "variant": "debug" }, "cpython-3.12.6-darwin-aarch64-none": { "name": "cpython", @@ -405,7 +1075,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "0419bafa4444a5aa0c554197bce0679e7cc0f28edc7ee8cfbe0ccea860bdb904" + "sha256": "0419bafa4444a5aa0c554197bce0679e7cc0f28edc7ee8cfbe0ccea860bdb904", + "variant": null }, "cpython-3.12.6-darwin-x86_64-none": { "name": "cpython", @@ -417,7 +1088,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "b10d19eb5548a3b3b0a5e6f9109834d7ecfc139bc15754f81a94d39eaa5bdd26" + "sha256": "b10d19eb5548a3b3b0a5e6f9109834d7ecfc139bc15754f81a94d39eaa5bdd26", + "variant": null }, "cpython-3.12.6-linux-aarch64-gnu": { "name": "cpython", @@ -429,7 +1101,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "22d119ac7df7f0bddfd4dfd075bcc4eb2532ed3df0bdba0579106835d49ef9cd" + "sha256": "22d119ac7df7f0bddfd4dfd075bcc4eb2532ed3df0bdba0579106835d49ef9cd", + "variant": null }, "cpython-3.12.6-linux-armv7-gnueabi": { "name": "cpython", @@ -441,7 +1114,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "190c23eb3b9c6b9638f69dc7fb829df8967ad64c82e82c93898a4d878d18ed2a" + "sha256": "190c23eb3b9c6b9638f69dc7fb829df8967ad64c82e82c93898a4d878d18ed2a", + "variant": null }, "cpython-3.12.6-linux-armv7-gnueabihf": { "name": "cpython", @@ -453,7 +1127,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "31a043c40e1dbb528404ff6e1fcad25638d54dfab2d379c3989d47ec24e6938b" + "sha256": "31a043c40e1dbb528404ff6e1fcad25638d54dfab2d379c3989d47ec24e6938b", + "variant": null }, "cpython-3.12.6-linux-powerpc64le-gnu": { "name": "cpython", @@ -465,7 +1140,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "fb49374b512b0e9f2cd2a720b3836f8a04228d73eb0786e64221eb55979edc6e" + "sha256": "fb49374b512b0e9f2cd2a720b3836f8a04228d73eb0786e64221eb55979edc6e", + "variant": null }, "cpython-3.12.6-linux-s390x-gnu": { "name": "cpython", @@ -477,7 +1153,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "89be19666ecb7cdbbfd596e462d690a78a380f1fe5c2967b25a1779b0cec9339" + "sha256": "89be19666ecb7cdbbfd596e462d690a78a380f1fe5c2967b25a1779b0cec9339", + "variant": null }, "cpython-3.12.6-linux-x86_64-gnu": { "name": "cpython", @@ -489,7 +1166,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b080463e4f0c452e592cdac1ca97936a6a19bb3d9a64da669a50ca843fce0108" + "sha256": "b080463e4f0c452e592cdac1ca97936a6a19bb3d9a64da669a50ca843fce0108", + "variant": null }, "cpython-3.12.6-linux-x86_64-musl": { "name": "cpython", @@ -501,7 +1179,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "661e2a4b03d6eccbb5b15f5bd2869fbdd39132513394d758287e46115e48d4ef" + "sha256": "661e2a4b03d6eccbb5b15f5bd2869fbdd39132513394d758287e46115e48d4ef", + "variant": null }, "cpython-3.12.6-windows-i686-none": { "name": "cpython", @@ -513,7 +1192,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d87275e613632ab738528fe20a94a7193e824e91ba7f1e7845e7fcfc1f114900" + "sha256": "d87275e613632ab738528fe20a94a7193e824e91ba7f1e7845e7fcfc1f114900", + "variant": null }, "cpython-3.12.6-windows-x86_64-none": { "name": "cpython", @@ -525,7 +1205,99 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "fe9898060f52c2171c2aa074f470f91339bdcf9896dae6709021c914f58aa863" + "sha256": "fe9898060f52c2171c2aa074f470f91339bdcf9896dae6709021c914f58aa863", + "variant": null + }, + "cpython-3.12.6+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ddddd8a1446754a75a637c35f61df33e2b99ea679455971dcabb336ffefb77db", + "variant": "debug" + }, + "cpython-3.12.6+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 12, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "1a4736063292701b4ed08f2f3c688647c4702e5381d1d1c208970f9bad68a457", + "variant": "debug" + }, + "cpython-3.12.6+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 12, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "d1c9924e90db87826bfd4ffc5189ec8983962f95d09fb79cdbd7f75bb4d88daa", + "variant": "debug" + }, + "cpython-3.12.6+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "edb11773eab6a91cd9407f1f8bf7055233453cf48c49cb300adca85048d8e887", + "variant": "debug" + }, + "cpython-3.12.6+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d21426d4356bb5b0525465e9cc39c27dba7c39e7112855ea2cf90a194c2bdf42", + "variant": "debug" + }, + "cpython-3.12.6+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "110a8ba95943af6b30198c7f925bb655d4257704abe28b4cfddc374ff367312e", + "variant": "debug" + }, + "cpython-3.12.6+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1d678f6f70dc0cf32c6f5edd18f81a560ff087c7d1bb4185a810ccd1f145b0c3", + "variant": "debug" }, "cpython-3.12.5-darwin-aarch64-none": { "name": "cpython", @@ -537,7 +1309,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "90715cdab075e5a2680acf2695572d165b6269bdb5d1942ab577491478aea55f" + "sha256": "90715cdab075e5a2680acf2695572d165b6269bdb5d1942ab577491478aea55f", + "variant": null }, "cpython-3.12.5-darwin-x86_64-none": { "name": "cpython", @@ -549,7 +1322,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "49a9f7ad41d62e0ece9e664ca5ae95f022e7b68eef48e8a6f11620ec9247c686" + "sha256": "49a9f7ad41d62e0ece9e664ca5ae95f022e7b68eef48e8a6f11620ec9247c686", + "variant": null }, "cpython-3.12.5-linux-aarch64-gnu": { "name": "cpython", @@ -561,7 +1335,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "06e512178cb513658a01c054b3eafc649ca362ccbeb02a6ae8a55b02c1ba75ca" + "sha256": "06e512178cb513658a01c054b3eafc649ca362ccbeb02a6ae8a55b02c1ba75ca", + "variant": null }, "cpython-3.12.5-linux-armv7-gnueabi": { "name": "cpython", @@ -573,7 +1348,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "7a584de9c2824f43d7a7b1c26eb61a18af770ebd603a74b45d57601ba62ba508" + "sha256": "7a584de9c2824f43d7a7b1c26eb61a18af770ebd603a74b45d57601ba62ba508", + "variant": null }, "cpython-3.12.5-linux-armv7-gnueabihf": { "name": "cpython", @@ -585,7 +1361,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "a9992b30d7b3ecb558cd12fde919e3e2836f161f8f777afea31140d5fff6362e" + "sha256": "a9992b30d7b3ecb558cd12fde919e3e2836f161f8f777afea31140d5fff6362e", + "variant": null }, "cpython-3.12.5-linux-powerpc64le-gnu": { "name": "cpython", @@ -597,7 +1374,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3bea081f4e6fa67e600a6a791bcfebb2891531ede2c21e23e1b7321b3369c737" + "sha256": "3bea081f4e6fa67e600a6a791bcfebb2891531ede2c21e23e1b7321b3369c737", + "variant": null }, "cpython-3.12.5-linux-s390x-gnu": { "name": "cpython", @@ -609,7 +1387,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2b6ea3a5242de99574191ee42df864756eca6d7cb1dbd4cd7ab2850ba8b828f8" + "sha256": "2b6ea3a5242de99574191ee42df864756eca6d7cb1dbd4cd7ab2850ba8b828f8", + "variant": null }, "cpython-3.12.5-linux-x86_64-gnu": { "name": "cpython", @@ -621,7 +1400,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "10680b593b5e31833218fd83104dee74af970a3463403a22bae613b952a34e8d" + "sha256": "10680b593b5e31833218fd83104dee74af970a3463403a22bae613b952a34e8d", + "variant": null }, "cpython-3.12.5-linux-x86_64-musl": { "name": "cpython", @@ -633,7 +1413,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "e61b1274e1195f227cb30ba5d89ea32d743796d992adcaffad4819e4b0405d24" + "sha256": "e61b1274e1195f227cb30ba5d89ea32d743796d992adcaffad4819e4b0405d24", + "variant": null }, "cpython-3.12.5-windows-i686-none": { "name": "cpython", @@ -645,7 +1426,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "b1009d46b87330c099d02411ca5e9e333f13305c5abdbe20810a7c467cedb051" + "sha256": "b1009d46b87330c099d02411ca5e9e333f13305c5abdbe20810a7c467cedb051", + "variant": null }, "cpython-3.12.5-windows-x86_64-none": { "name": "cpython", @@ -657,7 +1439,99 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "6eb0398795e8875575934cf21cdc9c7c7acddb46f9a52f91fdad509723f2f0e9" + "sha256": "6eb0398795e8875575934cf21cdc9c7c7acddb46f9a52f91fdad509723f2f0e9", + "variant": null + }, + "cpython-3.12.5+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "66ba3a4ee2d2196ce2d05275aa2c12c1cc4480530dadf54d6754e85cb11fa3da", + "variant": "debug" + }, + "cpython-3.12.5+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 12, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "6617a003ff280523f6862ec2dcf7e900e999d4521e4b9363c7c32245fb12df12", + "variant": "debug" + }, + "cpython-3.12.5+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 12, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "d8d9bbb21efdd6b4fbf5e004d57e7fad772ed1263615a6b2579803e9f2289dfa", + "variant": "debug" + }, + "cpython-3.12.5+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "895d2db8a2074578c86ce3f5499787923188bf559180b5cbd8401bae060a3f5e", + "variant": "debug" + }, + "cpython-3.12.5+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e0a8bfe653421e687ed56bcc4269b3e65390529c61581193252ce689c12f128b", + "variant": "debug" + }, + "cpython-3.12.5+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "580caa10b1c661409d35623d20b2b56f6c8f4c263122b0e8228a3cdaa482c8be", + "variant": "debug" + }, + "cpython-3.12.5+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a2766d5627353e8ce8a8c43b0b5c3007a77f4f095bf773739f5bb936860546cd", + "variant": "debug" }, "cpython-3.12.4-darwin-aarch64-none": { "name": "cpython", @@ -669,7 +1543,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "ef6948e836f531bd7a58ffbe602803ff1c83c65f99d1da19be369ea61f136c93" + "sha256": "ef6948e836f531bd7a58ffbe602803ff1c83c65f99d1da19be369ea61f136c93", + "variant": null }, "cpython-3.12.4-darwin-x86_64-none": { "name": "cpython", @@ -681,7 +1556,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "9d68cbdd12d1d6f98d35cc76add232c12db75c6b7f49733bffc88e7b1c025a79" + "sha256": "9d68cbdd12d1d6f98d35cc76add232c12db75c6b7f49733bffc88e7b1c025a79", + "variant": null }, "cpython-3.12.4-linux-aarch64-gnu": { "name": "cpython", @@ -693,7 +1569,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6c9cf13644edc7250525ab1b2529ba1c0fff56c0c5a5c2242d84b6d4889d2bea" + "sha256": "6c9cf13644edc7250525ab1b2529ba1c0fff56c0c5a5c2242d84b6d4889d2bea", + "variant": null }, "cpython-3.12.4-linux-armv7-gnueabi": { "name": "cpython", @@ -705,7 +1582,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "5a23ed8eaf948fe48d7c05dbfb58ea8638dcd2c4880d8519e069281ab427cbcb" + "sha256": "5a23ed8eaf948fe48d7c05dbfb58ea8638dcd2c4880d8519e069281ab427cbcb", + "variant": null }, "cpython-3.12.4-linux-armv7-gnueabihf": { "name": "cpython", @@ -717,7 +1595,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "4281764e69339a138e30211b9923d74036d07c7a56c6aacc6dbdb2802a575f51" + "sha256": "4281764e69339a138e30211b9923d74036d07c7a56c6aacc6dbdb2802a575f51", + "variant": null }, "cpython-3.12.4-linux-powerpc64le-gnu": { "name": "cpython", @@ -729,7 +1608,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "35a8359f1dc17a7a70007dae102a5e1562c0715a721377ede92137b2a0292406" + "sha256": "35a8359f1dc17a7a70007dae102a5e1562c0715a721377ede92137b2a0292406", + "variant": null }, "cpython-3.12.4-linux-s390x-gnu": { "name": "cpython", @@ -741,7 +1621,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b2fd015ab3689e024de6fbb34a4942acdb54c2184d1963e22829aafa1d81ba2c" + "sha256": "b2fd015ab3689e024de6fbb34a4942acdb54c2184d1963e22829aafa1d81ba2c", + "variant": null }, "cpython-3.12.4-linux-x86_64-gnu": { "name": "cpython", @@ -753,7 +1634,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ca076aee4329f53f988346eb0521ad2a2cf7f723b6296088d03b98d8f22f5420" + "sha256": "ca076aee4329f53f988346eb0521ad2a2cf7f723b6296088d03b98d8f22f5420", + "variant": null }, "cpython-3.12.4-linux-x86_64-musl": { "name": "cpython", @@ -765,7 +1647,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "de4983ffa610ff2c3b9bcb62882366f017d94bf11b194c1fce17ad9e502acce6" + "sha256": "de4983ffa610ff2c3b9bcb62882366f017d94bf11b194c1fce17ad9e502acce6", + "variant": null }, "cpython-3.12.4-windows-i686-none": { "name": "cpython", @@ -777,7 +1660,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "ff0fab24f38c22130e45b90b7ec10dc4ce9677b545d9fb9109a72d2ffbab7b02" + "sha256": "ff0fab24f38c22130e45b90b7ec10dc4ce9677b545d9fb9109a72d2ffbab7b02", + "variant": null }, "cpython-3.12.4-windows-x86_64-none": { "name": "cpython", @@ -789,7 +1673,99 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "6dd7b4607f8a25f0f5f68e745f4c572b1a20c3bbfa86accfa45b52ab93b18ece" + "sha256": "6dd7b4607f8a25f0f5f68e745f4c572b1a20c3bbfa86accfa45b52ab93b18ece", + "variant": null + }, + "cpython-3.12.4+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "35d7ef1f4f4849ad92ba6e29c46235b9f5a2951758bec1f9fcec4133744d8c02", + "variant": "debug" + }, + "cpython-3.12.4+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 12, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "541305f79e3e007f6313faf6e4a955ed47c08704dddb216bf6c810c30e8ec853", + "variant": "debug" + }, + "cpython-3.12.4+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 12, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "f110f5db3d2ff9db5590d77f4af655202d570b9d9dff8d8aafbf67dea88b9dbf", + "variant": "debug" + }, + "cpython-3.12.4+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "342e9d3c36f8da4e57fb065641f7e6ae98af110fbddc641b88d6930cd1895943", + "variant": "debug" + }, + "cpython-3.12.4+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "f8df0b7ebd2893e845fbbeb8f133e317b49223fc128823f6743cbfd7a50bfc0d", + "variant": "debug" + }, + "cpython-3.12.4+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "54324b36cf7b68af3ddbabd1ec482691a23bf059a7f46a8cb3f3615f5fd5d86d", + "variant": "debug" + }, + "cpython-3.12.4+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "797b3d36a9df38925b7a7c5facb47e56a0d1c4031ae7b121ce41c07433fa1d2e", + "variant": "debug" }, "cpython-3.12.3-darwin-aarch64-none": { "name": "cpython", @@ -801,7 +1777,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "ccc40e5af329ef2af81350db2a88bbd6c17b56676e82d62048c15d548401519e" + "sha256": "ccc40e5af329ef2af81350db2a88bbd6c17b56676e82d62048c15d548401519e", + "variant": null }, "cpython-3.12.3-darwin-x86_64-none": { "name": "cpython", @@ -813,7 +1790,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "c37a22fca8f57d4471e3708de6d13097668c5f160067f264bb2b18f524c890c8" + "sha256": "c37a22fca8f57d4471e3708de6d13097668c5f160067f264bb2b18f524c890c8", + "variant": null }, "cpython-3.12.3-linux-aarch64-gnu": { "name": "cpython", @@ -825,7 +1803,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "ec8126de97945e629cca9aedc80a29c4ae2992c9d69f2655e27ae73906ba187d" + "sha256": "ec8126de97945e629cca9aedc80a29c4ae2992c9d69f2655e27ae73906ba187d", + "variant": null }, "cpython-3.12.3-linux-armv7-gnueabi": { "name": "cpython", @@ -837,7 +1816,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-armv7-unknown-linux-gnueabi-install_only.tar.gz", - "sha256": "f693dd22b69361c17076157889eb8f1ce1a5ea670c031fae46782481ad892a64" + "sha256": "f693dd22b69361c17076157889eb8f1ce1a5ea670c031fae46782481ad892a64", + "variant": null }, "cpython-3.12.3-linux-armv7-gnueabihf": { "name": "cpython", @@ -849,7 +1829,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-armv7-unknown-linux-gnueabihf-install_only.tar.gz", - "sha256": "635080827bed4616dc271545677837203098e5b55e7195d803e1dca7da24fc0c" + "sha256": "635080827bed4616dc271545677837203098e5b55e7195d803e1dca7da24fc0c", + "variant": null }, "cpython-3.12.3-linux-powerpc64le-gnu": { "name": "cpython", @@ -861,7 +1842,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c5dcf08b8077e617d949bda23027c49712f583120b3ed744f9b143da1d580572" + "sha256": "c5dcf08b8077e617d949bda23027c49712f583120b3ed744f9b143da1d580572", + "variant": null }, "cpython-3.12.3-linux-s390x-gnu": { "name": "cpython", @@ -873,7 +1855,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "872fc321363b8cdd826fd2cb1adfd1ceb813bc1281f9d410c1c2c4e177e8df86" + "sha256": "872fc321363b8cdd826fd2cb1adfd1ceb813bc1281f9d410c1c2c4e177e8df86", + "variant": null }, "cpython-3.12.3-linux-x86_64-gnu": { "name": "cpython", @@ -885,7 +1868,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "a73ba777b5d55ca89edef709e6b8521e3f3d4289581f174c8699adfb608d09d6" + "sha256": "a73ba777b5d55ca89edef709e6b8521e3f3d4289581f174c8699adfb608d09d6", + "variant": null }, "cpython-3.12.3-linux-x86_64-musl": { "name": "cpython", @@ -897,7 +1881,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "eb70814dc254f02714c77305de01b8ed2250c146320e22d0ed14b39021f89a8a" + "sha256": "eb70814dc254f02714c77305de01b8ed2250c146320e22d0ed14b39021f89a8a", + "variant": null }, "cpython-3.12.3-windows-i686-none": { "name": "cpython", @@ -909,7 +1894,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-i686-pc-windows-msvc-install_only.tar.gz", - "sha256": "bd723ad1aa05551627715a428660250f0e74db0f1421b03f399235772057ef55" + "sha256": "bd723ad1aa05551627715a428660250f0e74db0f1421b03f399235772057ef55", + "variant": null }, "cpython-3.12.3-windows-x86_64-none": { "name": "cpython", @@ -921,7 +1907,99 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-pc-windows-msvc-install_only.tar.gz", - "sha256": "f7cfa4ad072feb4578c8afca5ba9a54ad591d665a441dd0d63aa366edbe19279" + "sha256": "f7cfa4ad072feb4578c8afca5ba9a54ad591d665a441dd0d63aa366edbe19279", + "variant": null + }, + "cpython-3.12.3+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "24daaf20123ac4b2b8657c1ac8227d391d2e5769d81237b68ee674569d307ad0", + "variant": "debug" + }, + "cpython-3.12.3+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 12, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "d4d76d1dfd2d1e344ab825d2991887fa31702004470a97d205c32b9e5541892a", + "variant": "debug" + }, + "cpython-3.12.3+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 12, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "96a7a3725bae8cc0933eaaaffe99c6a71218b6593c28af042a9fa8c34fc722d3", + "variant": "debug" + }, + "cpython-3.12.3+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "983f056cb8336a54364c2eec3d33808dd74f953f82b720c6a73ad943643b4920", + "variant": "debug" + }, + "cpython-3.12.3+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a009c51b178519e60b6b7848b0ea91f3a92007ca1bdf0a2e8b26b8b7a138cdc0", + "variant": "debug" + }, + "cpython-3.12.3+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ded92cd034b33df953c490d3343ef187ac065d1fcd78e8cee894be197c5f977e", + "variant": "debug" + }, + "cpython-3.12.3+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "81d9fc9ffd6860229e09b11be5d800db5966080ba6f4b7524ae7917423fd09c6", + "variant": "debug" }, "cpython-3.12.2-darwin-aarch64-none": { "name": "cpython", @@ -933,7 +2011,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "01c064c00013b0175c7858b159989819ead53f4746d40580b5b0b35b6e80fba6" + "sha256": "01c064c00013b0175c7858b159989819ead53f4746d40580b5b0b35b6e80fba6", + "variant": null }, "cpython-3.12.2-darwin-x86_64-none": { "name": "cpython", @@ -945,7 +2024,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "a53a6670a202c96fec0b8c55ccc780ea3af5307eb89268d5b41a9775b109c094" + "sha256": "a53a6670a202c96fec0b8c55ccc780ea3af5307eb89268d5b41a9775b109c094", + "variant": null }, "cpython-3.12.2-linux-aarch64-gnu": { "name": "cpython", @@ -957,7 +2037,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "e52550379e7c4ac27a87de832d172658bc04150e4e27d4e858e6d8cbb96fd709" + "sha256": "e52550379e7c4ac27a87de832d172658bc04150e4e27d4e858e6d8cbb96fd709", + "variant": null }, "cpython-3.12.2-linux-powerpc64le-gnu": { "name": "cpython", @@ -969,7 +2050,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "74bc02c4bbbd26245c37b29b9e12d0a9c1b7ab93477fed8b651c988b6a9a6251" + "sha256": "74bc02c4bbbd26245c37b29b9e12d0a9c1b7ab93477fed8b651c988b6a9a6251", + "variant": null }, "cpython-3.12.2-linux-s390x-gnu": { "name": "cpython", @@ -981,7 +2063,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "ecd6b0285e5eef94deb784b588b4b425a15a43ae671bf206556659dc141a9825" + "sha256": "ecd6b0285e5eef94deb784b588b4b425a15a43ae671bf206556659dc141a9825", + "variant": null }, "cpython-3.12.2-linux-x86_64-gnu": { "name": "cpython", @@ -993,7 +2076,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "57a37b57f8243caa4cdac016176189573ad7620f0b6da5941c5e40660f9468ab" + "sha256": "57a37b57f8243caa4cdac016176189573ad7620f0b6da5941c5e40660f9468ab", + "variant": null }, "cpython-3.12.2-linux-x86_64-musl": { "name": "cpython", @@ -1005,7 +2089,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "b428b4151c70b85339ac2659e5f69f7e47142d34a506e05ecd095efe2e3dec81" + "sha256": "b428b4151c70b85339ac2659e5f69f7e47142d34a506e05ecd095efe2e3dec81", + "variant": null }, "cpython-3.12.2-windows-i686-none": { "name": "cpython", @@ -1017,7 +2102,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "1e919365f3e04eb111283f7a45d32eac2f327287ab7bf46720d5629e144cbff9" + "sha256": "1e919365f3e04eb111283f7a45d32eac2f327287ab7bf46720d5629e144cbff9", + "variant": null }, "cpython-3.12.2-windows-x86_64-none": { "name": "cpython", @@ -1029,7 +2115,73 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "1e5655a6ccb1a64a78460e4e3ee21036c70246800f176a6c91043a3fe3654a3b" + "sha256": "1e5655a6ccb1a64a78460e4e3ee21036c70246800f176a6c91043a3fe3654a3b", + "variant": null + }, + "cpython-3.12.2+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 2, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.12.2+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 2, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.12.2+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 2, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.12.2+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 2, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.12.2+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2f5f088639e17981b0aeeeeab0fbb6858002d5f10bf57e26eaf32f99b4b6c765", + "variant": "debug" }, "cpython-3.12.1-darwin-aarch64-none": { "name": "cpython", @@ -1041,7 +2193,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "f93f8375ca6ac0a35d58ff007043cbd3a88d9609113f1cb59cf7c8d215f064af" + "sha256": "f93f8375ca6ac0a35d58ff007043cbd3a88d9609113f1cb59cf7c8d215f064af", + "variant": null }, "cpython-3.12.1-darwin-x86_64-none": { "name": "cpython", @@ -1053,7 +2206,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "eca96158c1568dedd9a0b3425375637a83764d1fa74446438293089a8bfac1f8" + "sha256": "eca96158c1568dedd9a0b3425375637a83764d1fa74446438293089a8bfac1f8", + "variant": null }, "cpython-3.12.1-linux-aarch64-gnu": { "name": "cpython", @@ -1065,7 +2219,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "236533ef20e665007a111c2f36efb59c87ae195ad7dca223b6dc03fb07064f0b" + "sha256": "236533ef20e665007a111c2f36efb59c87ae195ad7dca223b6dc03fb07064f0b", + "variant": null }, "cpython-3.12.1-linux-powerpc64le-gnu": { "name": "cpython", @@ -1077,7 +2232,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "78051f0d1411ee62bc2af5edfccf6e8400ac4ef82887a2affc19a7ace6a05267" + "sha256": "78051f0d1411ee62bc2af5edfccf6e8400ac4ef82887a2affc19a7ace6a05267", + "variant": null }, "cpython-3.12.1-linux-s390x-gnu": { "name": "cpython", @@ -1089,7 +2245,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "60631211c701f8d2c56e5dd7b154e68868128a019b9db1d53a264f56c0d4aee2" + "sha256": "60631211c701f8d2c56e5dd7b154e68868128a019b9db1d53a264f56c0d4aee2", + "variant": null }, "cpython-3.12.1-linux-x86_64-gnu": { "name": "cpython", @@ -1101,7 +2258,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "74e330b8212ca22fd4d9a2003b9eec14892155566738febc8e5e572f267b9472" + "sha256": "74e330b8212ca22fd4d9a2003b9eec14892155566738febc8e5e572f267b9472", + "variant": null }, "cpython-3.12.1-linux-x86_64-musl": { "name": "cpython", @@ -1113,7 +2271,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "876389f071d62ee9a4bdd7ce31e69c3cdd256fe498e4dd6bb2b80e674e7351fe" + "sha256": "876389f071d62ee9a4bdd7ce31e69c3cdd256fe498e4dd6bb2b80e674e7351fe", + "variant": null }, "cpython-3.12.1-windows-i686-none": { "name": "cpython", @@ -1125,7 +2284,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "13c8a6f337a4e1ef043ffb8ea3c218ab2073afe0d3be36fcdf8ceb6f757210e8" + "sha256": "13c8a6f337a4e1ef043ffb8ea3c218ab2073afe0d3be36fcdf8ceb6f757210e8", + "variant": null }, "cpython-3.12.1-windows-x86_64-none": { "name": "cpython", @@ -1137,7 +2297,73 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "fd5a9e0f41959d0341246d3643f2b8794f638adc0cec8dd5e1b6465198eae08a" + "sha256": "fd5a9e0f41959d0341246d3643f2b8794f638adc0cec8dd5e1b6465198eae08a", + "variant": null + }, + "cpython-3.12.1+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 1, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.12.1+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 1, + "prerelease": "", + "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": "b61686ce05c58c913e4fdb7e7c7105ed36d9bcdcd1a841e7f08b243f40d5cf77", + "variant": "debug" + }, + "cpython-3.12.1+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 1, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.12.1+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 1, + "prerelease": "", + "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": "89ef67b617b8c9804965509b2d256f53439ceede83b5b64085315f038ad81e60", + "variant": "debug" + }, + "cpython-3.12.1+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 1, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "0823ed21f7b79129677c51c6a73d3ca53a37179931a5a40a1d53b565d54679ec", + "variant": "debug" }, "cpython-3.12.0-darwin-aarch64-none": { "name": "cpython", @@ -1149,7 +2375,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "4734a2be2becb813830112c780c9879ac3aff111a0b0cd590e65ec7465774d02" + "sha256": "4734a2be2becb813830112c780c9879ac3aff111a0b0cd590e65ec7465774d02", + "variant": null }, "cpython-3.12.0-darwin-x86_64-none": { "name": "cpython", @@ -1161,7 +2388,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "5a9e88c8aa52b609d556777b52ebde464ae4b4f77e4aac4eb693af57395c9abf" + "sha256": "5a9e88c8aa52b609d556777b52ebde464ae4b4f77e4aac4eb693af57395c9abf", + "variant": null }, "cpython-3.12.0-linux-aarch64-gnu": { "name": "cpython", @@ -1173,7 +2401,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "bccfe67cf5465a3dfb0336f053966e2613a9bc85a6588c2fcf1366ef930c4f88" + "sha256": "bccfe67cf5465a3dfb0336f053966e2613a9bc85a6588c2fcf1366ef930c4f88", + "variant": null }, "cpython-3.12.0-linux-powerpc64le-gnu": { "name": "cpython", @@ -1185,7 +2414,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "b5dae075467ace32c594c7877fe6ebe0837681f814601d5d90ba4c0dfd87a1f2" + "sha256": "b5dae075467ace32c594c7877fe6ebe0837681f814601d5d90ba4c0dfd87a1f2", + "variant": null }, "cpython-3.12.0-linux-s390x-gnu": { "name": "cpython", @@ -1197,7 +2427,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "5681621349dd85d9726d1b67c84a9686ce78f72e73a6f9e4cc4119911655759e" + "sha256": "5681621349dd85d9726d1b67c84a9686ce78f72e73a6f9e4cc4119911655759e", + "variant": null }, "cpython-3.12.0-linux-x86_64-gnu": { "name": "cpython", @@ -1209,7 +2440,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "e51a5293f214053ddb4645b2c9f84542e2ef86870b8655704367bd4b29d39fe9" + "sha256": "e51a5293f214053ddb4645b2c9f84542e2ef86870b8655704367bd4b29d39fe9", + "variant": null }, "cpython-3.12.0-linux-x86_64-musl": { "name": "cpython", @@ -1221,7 +2453,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "922f9404f39dc4edb8558a93cef5c3330895a4c87acb1de2a2cf662ab942dbe5" + "sha256": "922f9404f39dc4edb8558a93cef5c3330895a4c87acb1de2a2cf662ab942dbe5", + "variant": null }, "cpython-3.12.0-windows-i686-none": { "name": "cpython", @@ -1233,7 +2466,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "6e4f30a998245cfaef00d1b87f8fd5f6c250bd222f933f8f38f124d4f03227f9" + "sha256": "6e4f30a998245cfaef00d1b87f8fd5f6c250bd222f933f8f38f124d4f03227f9", + "variant": null }, "cpython-3.12.0-windows-x86_64-none": { "name": "cpython", @@ -1245,7 +2479,73 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "facfaa1fbc8653f95057f3c4a0f8aa833dab0e0b316e24ee8686bc761d4b4f8d" + "sha256": "facfaa1fbc8653f95057f3c4a0f8aa833dab0e0b316e24ee8686bc761d4b4f8d", + "variant": null + }, + "cpython-3.12.0+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 0, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.12.0+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 0, + "prerelease": "", + "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": "800a89873e30e24bb1b6075f8cd718964537c5ba62bcdbefdcdae4de68ddccc4", + "variant": "debug" + }, + "cpython-3.12.0+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 0, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.12.0+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 12, + "patch": 0, + "prerelease": "", + "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": "a8c38cd2e53136c579632e2938d1b857f22e496c7dba99ad9a7ad6a67b43274a", + "variant": "debug" + }, + "cpython-3.12.0+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 12, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "0b4380904d53f3322d3e5276de47bfa91a19289b7c734494c127ed0793017dde", + "variant": "debug" }, "cpython-3.11.10-darwin-aarch64-none": { "name": "cpython", @@ -1256,8 +2556,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "263b1b50ff705ab064c50c3165de9df2ed712a77cd81c55dd340c3172e103687" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a5a224138a526acecfd17210953d76a28487968a767204902e2bde809bb0e759", + "variant": null }, "cpython-3.11.10-darwin-x86_64-none": { "name": "cpython", @@ -1268,8 +2569,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "a8174aef8a662201d1bb4680571cf383e89f5b44dc06614df5928595ddf82876" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "575b49a7aa64e97b06de605b7e947033bf2310b5bc5f9aedb9859d4745033d91", + "variant": null }, "cpython-3.11.10-linux-aarch64-gnu": { "name": "cpython", @@ -1280,8 +2582,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "723ec57b64349d748bbd55579be8a08c0d3633267f0c5820eb75df598792c975" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9d124604ffdea4fbaabb10b343c5a36b636a3e7b94dfc1cccd4531f33fceae5e", + "variant": null }, "cpython-3.11.10-linux-armv7-gnueabi": { "name": "cpython", @@ -1292,8 +2595,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "bb3bd40fd5f0490534d52f8d465af6b91a7e0e7291d020d9bbd95350a0638238" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "deb089a5ac0fbd9ad2e3dc843d90019ead75b1ec895fd57a5abca190ba86cb77", + "variant": null }, "cpython-3.11.10-linux-armv7-gnueabihf": { "name": "cpython", @@ -1304,8 +2608,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "5f2b7e6b087e085ad6bfd6a43a49611ffcc47307e45bc65f8963717b2a59dc4c" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "3655da6f1ccde823fc03f790bebfff106825e2b5ec4b733be225150275cd6321", + "variant": null }, "cpython-3.11.10-linux-powerpc64le-gnu": { "name": "cpython", @@ -1316,8 +2621,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "34a2f075aba20e65cfc1973ef0108c369ac89564bae93506c49257215d3555a2" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "cc16cf0b1a1aa61f4e90d38ccaad0b65085cea69d2dcc2c6281ef9d4e6cccdd8", + "variant": null }, "cpython-3.11.10-linux-s390x-gnu": { "name": "cpython", @@ -1328,8 +2634,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "317320957770a4cdbe42f3810a67685045e1cd82adf545948ec64d9d566d0c4f" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e8017e3b916f8c7b8fbdf2bd5fc18c6eb7ce2397df240fbeea84b05d4c7a37a4", + "variant": null }, "cpython-3.11.10-linux-x86_64-gnu": { "name": "cpython", @@ -1340,8 +2647,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "34c58f0dfd84ce2b16cea07ca39c47ed8d7b7b3656dda63062e9e9db55d43ec4" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "03f15e19e2452641b6375b59ba094ff6cf2fc118315d24a6ca63ce60e4d4a6e0", + "variant": null }, "cpython-3.11.10-linux-x86_64-musl": { "name": "cpython", @@ -1352,8 +2660,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "b0c62b085e4adb7cc32be3a7dd267bc427c2ab588802f2918d90873ece382342" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "5b33f0ff29552f15daacf81c426ed585fae24987b47d614142a7906eae6f2b04", + "variant": null }, "cpython-3.11.10-windows-i686-none": { "name": "cpython", @@ -1364,8 +2673,9 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "4840b2fb1d421a38d8ffabf80350093e4283a67e85e67ba8285170b44b371ad9" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0a5b423517722e9868ac4a63893f24f24db9bd67e8679e6e448343c5829d2e77", + "variant": null }, "cpython-3.11.10-windows-x86_64-none": { "name": "cpython", @@ -1376,8 +2686,100 @@ "minor": 11, "patch": 10, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "15a14bdc15a43119d74b7e3bde00031011d49d2a6068fe9743da92ba0c3d7b87" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ea770ebabc620ff46f1d0f905c774a9b8aa5834620e89617ad5e01f90d36b3ee", + "variant": null + }, + "cpython-3.11.10+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e752e3dd9b3a3aa5e58a198b17e60a8549f379e9b2573dfb639819958d3c846d", + "variant": "debug" + }, + "cpython-3.11.10+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 11, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "df34b59162738af981a067fdec5072f2633424eb49600cfac7a1612f8e1fa594", + "variant": "debug" + }, + "cpython-3.11.10+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 11, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "863daf9c91adf7fc118d5422c779541115859edec3a5dec6dd3a343173d2e028", + "variant": "debug" + }, + "cpython-3.11.10+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d2323b4e3b4e2862bbe995dbb13f96e66f9dec8adc58f4cd40c6e7f865ff3af0", + "variant": "debug" + }, + "cpython-3.11.10+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fae12014fe44f3eae3363ef9bf6b0edf214f82dccdc73ae82020225173093678", + "variant": "debug" + }, + "cpython-3.11.10+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a51ec678e86286da1ac4a0d7ef85eef2e889018631f0463e6b871b1a3591ad65", + "variant": "debug" + }, + "cpython-3.11.10+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "b18e2b848bbbf75ecda2329dd2aef00bf603dc510c8775cc36bb9cec0318f6e2", + "variant": "debug" }, "cpython-3.11.9-darwin-aarch64-none": { "name": "cpython", @@ -1389,7 +2791,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "c4e2f7774421bcb381245945e132419b529399dfa4a56059acda1493751fa377" + "sha256": "c4e2f7774421bcb381245945e132419b529399dfa4a56059acda1493751fa377", + "variant": null }, "cpython-3.11.9-darwin-x86_64-none": { "name": "cpython", @@ -1401,7 +2804,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "c8680f90137e36b54b3631271ccdfe5de363e7d563d8df87c53e11b956a00e04" + "sha256": "c8680f90137e36b54b3631271ccdfe5de363e7d563d8df87c53e11b956a00e04", + "variant": null }, "cpython-3.11.9-linux-aarch64-gnu": { "name": "cpython", @@ -1413,7 +2817,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "364cf099524fff92c31b8ff5ae3f7b32b0fa6cf1d380c6e37cf56140d08dfc87" + "sha256": "364cf099524fff92c31b8ff5ae3f7b32b0fa6cf1d380c6e37cf56140d08dfc87", + "variant": null }, "cpython-3.11.9-linux-armv7-gnueabi": { "name": "cpython", @@ -1425,7 +2830,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "e64d3cf033c804e9c14aaf4ae746632c01894706098b20acbf00df4bd28d0b0e" + "sha256": "e64d3cf033c804e9c14aaf4ae746632c01894706098b20acbf00df4bd28d0b0e", + "variant": null }, "cpython-3.11.9-linux-armv7-gnueabihf": { "name": "cpython", @@ -1437,7 +2843,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "7630838c7602e6a6a56c41263d6a808a2a2004a7ea38770ffc4c7aaf34e169ae" + "sha256": "7630838c7602e6a6a56c41263d6a808a2a2004a7ea38770ffc4c7aaf34e169ae", + "variant": null }, "cpython-3.11.9-linux-powerpc64le-gnu": { "name": "cpython", @@ -1449,7 +2856,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2387479d17127e5b087f582bac948f859c25c4b38c64f558e0a399af7a8a0225" + "sha256": "2387479d17127e5b087f582bac948f859c25c4b38c64f558e0a399af7a8a0225", + "variant": null }, "cpython-3.11.9-linux-s390x-gnu": { "name": "cpython", @@ -1461,7 +2869,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "30c71053e9360471b7f350f1562ff4e42eb91ad2ca61b391295b5dea8b2b9efd" + "sha256": "30c71053e9360471b7f350f1562ff4e42eb91ad2ca61b391295b5dea8b2b9efd", + "variant": null }, "cpython-3.11.9-linux-x86_64-gnu": { "name": "cpython", @@ -1473,7 +2882,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "daa487c7e73005c4426ac393273117cf0e2dc4ab9b2eeda366e04cd00eea00c9" + "sha256": "daa487c7e73005c4426ac393273117cf0e2dc4ab9b2eeda366e04cd00eea00c9", + "variant": null }, "cpython-3.11.9-linux-x86_64-musl": { "name": "cpython", @@ -1485,7 +2895,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "b3e94cbf19bd08bf02f6e6945f6c2211453f601c7c6f79721da63a06bf99b1f9" + "sha256": "b3e94cbf19bd08bf02f6e6945f6c2211453f601c7c6f79721da63a06bf99b1f9", + "variant": null }, "cpython-3.11.9-windows-i686-none": { "name": "cpython", @@ -1497,7 +2908,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "091c99a210f4f401a305231f3f218ee3d5714658b8d3aac344d34efc716dff85" + "sha256": "091c99a210f4f401a305231f3f218ee3d5714658b8d3aac344d34efc716dff85", + "variant": null }, "cpython-3.11.9-windows-x86_64-none": { "name": "cpython", @@ -1509,7 +2921,99 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "8ac54a8d711ef0d49b62a2c3521c2d0403f1b221dc9d84c5f85fe48903e82523" + "sha256": "8ac54a8d711ef0d49b62a2c3521c2d0403f1b221dc9d84c5f85fe48903e82523", + "variant": null + }, + "cpython-3.11.9+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "11f1f93ccdc399c3d029593ca147ba457500ce24c2cbd7df2040f44352c9039a", + "variant": "debug" + }, + "cpython-3.11.9+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 11, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "2fe1c9df6e5ebd6ba6748207ca665e3087d35aa3ccacaa68fb7375e55c4d29e7", + "variant": "debug" + }, + "cpython-3.11.9+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 11, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "e30eb0c9d50e7fe232e1f10391470136c7812a73401348dbdc60418cd0334fbe", + "variant": "debug" + }, + "cpython-3.11.9+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2a7302fa80ff26db99d8afe3824aab961487ec9362729e15c1421bbdf9115f79", + "variant": "debug" + }, + "cpython-3.11.9+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "09161f37bed9742b6e94262e047a666acb7d43a5d4e679fc87d7d0a58c92ec70", + "variant": "debug" + }, + "cpython-3.11.9+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1fbae62bc303512d4024bb69cab26766a5a8d12386aa79ed03b5fd8f4c08c77b", + "variant": "debug" + }, + "cpython-3.11.9+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "dc4fd5dd161a0c09375457f29b2c03b1aa026702abbdaedb9db01d2ccc17650b", + "variant": "debug" }, "cpython-3.11.8-darwin-aarch64-none": { "name": "cpython", @@ -1521,7 +3025,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "389a51139f5abe071a0d70091ca5df3e7a3dfcfcbe3e0ba6ad85fb4c5638421e" + "sha256": "389a51139f5abe071a0d70091ca5df3e7a3dfcfcbe3e0ba6ad85fb4c5638421e", + "variant": null }, "cpython-3.11.8-darwin-x86_64-none": { "name": "cpython", @@ -1533,7 +3038,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "097f467b0c36706bfec13f199a2eaf924e668f70c6e2bd1f1366806962f7e86e" + "sha256": "097f467b0c36706bfec13f199a2eaf924e668f70c6e2bd1f1366806962f7e86e", + "variant": null }, "cpython-3.11.8-linux-aarch64-gnu": { "name": "cpython", @@ -1545,7 +3051,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "389b9005fb78dd5a6f68df5ea45ab7b30d9a4b3222af96999e94fd20d4ad0c6a" + "sha256": "389b9005fb78dd5a6f68df5ea45ab7b30d9a4b3222af96999e94fd20d4ad0c6a", + "variant": null }, "cpython-3.11.8-linux-powerpc64le-gnu": { "name": "cpython", @@ -1557,7 +3064,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "eb2b31f8e50309aae493c6a359c32b723a676f07c641f5e8fe4b6aa4dbb50946" + "sha256": "eb2b31f8e50309aae493c6a359c32b723a676f07c641f5e8fe4b6aa4dbb50946", + "variant": null }, "cpython-3.11.8-linux-s390x-gnu": { "name": "cpython", @@ -1569,7 +3077,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "844f64f4c16e24965778281da61d1e0e6cd1358a581df1662da814b1eed096b9" + "sha256": "844f64f4c16e24965778281da61d1e0e6cd1358a581df1662da814b1eed096b9", + "variant": null }, "cpython-3.11.8-linux-x86_64-gnu": { "name": "cpython", @@ -1581,7 +3090,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "94e13d0e5ad417035b80580f3e893a72e094b0900d5d64e7e34ab08e95439987" + "sha256": "94e13d0e5ad417035b80580f3e893a72e094b0900d5d64e7e34ab08e95439987", + "variant": null }, "cpython-3.11.8-linux-x86_64-musl": { "name": "cpython", @@ -1593,7 +3103,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "08e1ebf51b5965e23f8e68664d17274c1cdabb5b2d7509a2003920e5d58172c7" + "sha256": "08e1ebf51b5965e23f8e68664d17274c1cdabb5b2d7509a2003920e5d58172c7", + "variant": null }, "cpython-3.11.8-windows-i686-none": { "name": "cpython", @@ -1605,7 +3116,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "75039951f8f94d7304bc17b674af1668b9e1ea6d6c9ba1da28e90c0ad8030e3c" + "sha256": "75039951f8f94d7304bc17b674af1668b9e1ea6d6c9ba1da28e90c0ad8030e3c", + "variant": null }, "cpython-3.11.8-windows-x86_64-none": { "name": "cpython", @@ -1617,7 +3129,73 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "b618f1f047349770ee1ef11d1b05899840abd53884b820fd25c7dfe2ec1664d4" + "sha256": "b618f1f047349770ee1ef11d1b05899840abd53884b820fd25c7dfe2ec1664d4", + "variant": null + }, + "cpython-3.11.8+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 8, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.8+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 8, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.8+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 8, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.8+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 8, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.8+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 8, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "868adbcbef61c119d10f4da18ecab180423443aa64be0d6c79790df2ed1d12b7", + "variant": "debug" }, "cpython-3.11.7-darwin-aarch64-none": { "name": "cpython", @@ -1629,7 +3207,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "b042c966920cf8465385ca3522986b12d745151a72c060991088977ca36d3883" + "sha256": "b042c966920cf8465385ca3522986b12d745151a72c060991088977ca36d3883", + "variant": null }, "cpython-3.11.7-darwin-x86_64-none": { "name": "cpython", @@ -1641,7 +3220,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "a0e615eef1fafdc742da0008425a9030b7ea68a4ae4e73ac557ef27b112836d4" + "sha256": "a0e615eef1fafdc742da0008425a9030b7ea68a4ae4e73ac557ef27b112836d4", + "variant": null }, "cpython-3.11.7-linux-aarch64-gnu": { "name": "cpython", @@ -1653,7 +3233,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "b102eaf865eb715aa98a8a2ef19037b6cc3ae7dfd4a632802650f29de635aa13" + "sha256": "b102eaf865eb715aa98a8a2ef19037b6cc3ae7dfd4a632802650f29de635aa13", + "variant": null }, "cpython-3.11.7-linux-powerpc64le-gnu": { "name": "cpython", @@ -1665,7 +3246,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "b44e1b74afe75c7b19143413632c4386708ae229117f8f950c2094e9681d34c7" + "sha256": "b44e1b74afe75c7b19143413632c4386708ae229117f8f950c2094e9681d34c7", + "variant": null }, "cpython-3.11.7-linux-s390x-gnu": { "name": "cpython", @@ -1677,7 +3259,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "49520e3ff494708020f306e30b0964f079170be83e956be4504f850557378a22" + "sha256": "49520e3ff494708020f306e30b0964f079170be83e956be4504f850557378a22", + "variant": null }, "cpython-3.11.7-linux-x86_64-gnu": { "name": "cpython", @@ -1689,7 +3272,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "4a51ce60007a6facf64e5495f4cf322e311ba9f39a8cd3f3e4c026eae488e140" + "sha256": "4a51ce60007a6facf64e5495f4cf322e311ba9f39a8cd3f3e4c026eae488e140", + "variant": null }, "cpython-3.11.7-linux-x86_64-musl": { "name": "cpython", @@ -1701,7 +3285,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "1a919a35172eb9419eba841eeb0ec9879dbc2b006b284ee5c454c08197b50f74" + "sha256": "1a919a35172eb9419eba841eeb0ec9879dbc2b006b284ee5c454c08197b50f74", + "variant": null }, "cpython-3.11.7-windows-i686-none": { "name": "cpython", @@ -1713,7 +3298,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "f5a6ca1280749d8ceaf8851585ef6b0cd2f1f76e801a77c1d744019554eef2f0" + "sha256": "f5a6ca1280749d8ceaf8851585ef6b0cd2f1f76e801a77c1d744019554eef2f0", + "variant": null }, "cpython-3.11.7-windows-x86_64-none": { "name": "cpython", @@ -1725,7 +3311,73 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "67077e6fa918e4f4fd60ba169820b00be7c390c497bf9bc9cab2c255ea8e6f3e" + "sha256": "67077e6fa918e4f4fd60ba169820b00be7c390c497bf9bc9cab2c255ea8e6f3e", + "variant": null + }, + "cpython-3.11.7+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 7, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.7+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 7, + "prerelease": "", + "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": "016ed6470c599ea5cc4dbb9c3f3fe86be059ad4e1b6cd2df10e40b7ec6970f16", + "variant": "debug" + }, + "cpython-3.11.7+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 7, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.7+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 7, + "prerelease": "", + "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": "01bca7a2f457d4bd2b367640d9337d12b31db73d670a16500b7a751194942103", + "variant": "debug" + }, + "cpython-3.11.7+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "766fd4a583fdfbe65e99b1e3caea843d0eeefde5675d73f3214a53c17a832320", + "variant": "debug" }, "cpython-3.11.6-darwin-aarch64-none": { "name": "cpython", @@ -1737,7 +3389,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "916c35125b5d8323a21526d7a9154ca626453f63d0878e95b9f613a95006c990" + "sha256": "916c35125b5d8323a21526d7a9154ca626453f63d0878e95b9f613a95006c990", + "variant": null }, "cpython-3.11.6-darwin-x86_64-none": { "name": "cpython", @@ -1749,7 +3402,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "178cb1716c2abc25cb56ae915096c1a083e60abeba57af001996e8bc6ce1a371" + "sha256": "178cb1716c2abc25cb56ae915096c1a083e60abeba57af001996e8bc6ce1a371", + "variant": null }, "cpython-3.11.6-linux-aarch64-gnu": { "name": "cpython", @@ -1761,7 +3415,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "3e26a672df17708c4dc928475a5974c3fb3a34a9b45c65fb4bd1e50504cc84ec" + "sha256": "3e26a672df17708c4dc928475a5974c3fb3a34a9b45c65fb4bd1e50504cc84ec", + "variant": null }, "cpython-3.11.6-linux-powerpc64le-gnu": { "name": "cpython", @@ -1773,7 +3428,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "7937035f690a624dba4d014ffd20c342e843dd46f89b0b0a1e5726b85deb8eaf" + "sha256": "7937035f690a624dba4d014ffd20c342e843dd46f89b0b0a1e5726b85deb8eaf", + "variant": null }, "cpython-3.11.6-linux-s390x-gnu": { "name": "cpython", @@ -1785,7 +3441,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "f9f19823dba3209cedc4647b00f46ed0177242917db20fb7fb539970e384531c" + "sha256": "f9f19823dba3209cedc4647b00f46ed0177242917db20fb7fb539970e384531c", + "variant": null }, "cpython-3.11.6-linux-x86_64-gnu": { "name": "cpython", @@ -1797,7 +3454,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "ee37a7eae6e80148c7e3abc56e48a397c1664f044920463ad0df0fc706eacea8" + "sha256": "ee37a7eae6e80148c7e3abc56e48a397c1664f044920463ad0df0fc706eacea8", + "variant": null }, "cpython-3.11.6-linux-x86_64-musl": { "name": "cpython", @@ -1809,7 +3467,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "c929e5fe676ad20afcf6807a797d21261ae0827e84ec18742031a9582aed0d46" + "sha256": "c929e5fe676ad20afcf6807a797d21261ae0827e84ec18742031a9582aed0d46", + "variant": null }, "cpython-3.11.6-windows-i686-none": { "name": "cpython", @@ -1821,7 +3480,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "dd48b2cfaae841b4cd9beed23e2ae68b13527a065ef3d271d228735769c4e64d" + "sha256": "dd48b2cfaae841b4cd9beed23e2ae68b13527a065ef3d271d228735769c4e64d", + "variant": null }, "cpython-3.11.6-windows-x86_64-none": { "name": "cpython", @@ -1833,7 +3493,73 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "3933545e6d41462dd6a47e44133ea40995bc6efeed8c2e4cbdf1a699303e95ea" + "sha256": "3933545e6d41462dd6a47e44133ea40995bc6efeed8c2e4cbdf1a699303e95ea", + "variant": null + }, + "cpython-3.11.6+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 6, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.6+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 6, + "prerelease": "", + "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": "71c34db1165860a6bf458d817aef00dea96146130bf5f8bd7ee39b12892ef463", + "variant": "debug" + }, + "cpython-3.11.6+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 6, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.6+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 6, + "prerelease": "", + "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": "6e7889a15d861f1860ed84f3f5ea4586d198aa003b22556d91e180a44184dcd7", + "variant": "debug" + }, + "cpython-3.11.6+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c2178b505ac315ede0a2659511841acd022bc7290ef65648e052bb1acebff59f", + "variant": "debug" }, "cpython-3.11.5-darwin-aarch64-none": { "name": "cpython", @@ -1845,7 +3571,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "dab64b3580118ad2073babd7c29fd2053b616479df5c107d31fe2af1f45e948b" + "sha256": "dab64b3580118ad2073babd7c29fd2053b616479df5c107d31fe2af1f45e948b", + "variant": null }, "cpython-3.11.5-darwin-x86_64-none": { "name": "cpython", @@ -1857,7 +3584,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "4a4efa7378c72f1dd8ebcce1afb99b24c01b07023aa6b8fea50eaedb50bf2bfc" + "sha256": "4a4efa7378c72f1dd8ebcce1afb99b24c01b07023aa6b8fea50eaedb50bf2bfc", + "variant": null }, "cpython-3.11.5-linux-aarch64-gnu": { "name": "cpython", @@ -1869,7 +3597,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "bb5c5d1ea0f199fe2d3f0996fff4b48ca6ddc415a3dbd98f50bff7fce48aac80" + "sha256": "bb5c5d1ea0f199fe2d3f0996fff4b48ca6ddc415a3dbd98f50bff7fce48aac80", + "variant": null }, "cpython-3.11.5-linux-i686-gnu": { "name": "cpython", @@ -1881,7 +3610,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "82de7e2551c015145c017742a5c0411d67a7544595df43c02b5efa4762d5123e" + "sha256": "82de7e2551c015145c017742a5c0411d67a7544595df43c02b5efa4762d5123e", + "variant": null }, "cpython-3.11.5-linux-powerpc64le-gnu": { "name": "cpython", @@ -1893,7 +3623,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "14121b53e9c8c6d0741f911ae00102a35adbcf5c3cdf732687ef7617b7d7304d" + "sha256": "14121b53e9c8c6d0741f911ae00102a35adbcf5c3cdf732687ef7617b7d7304d", + "variant": null }, "cpython-3.11.5-linux-s390x-gnu": { "name": "cpython", @@ -1905,7 +3636,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "fe459da39874443579d6fe88c68777c6d3e331038e1fb92a0451879fb6beb16d" + "sha256": "fe459da39874443579d6fe88c68777c6d3e331038e1fb92a0451879fb6beb16d", + "variant": null }, "cpython-3.11.5-linux-x86_64-gnu": { "name": "cpython", @@ -1917,7 +3649,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "fbed6f7694b2faae5d7c401a856219c945397f772eea5ca50c6eb825cbc9d1e1" + "sha256": "fbed6f7694b2faae5d7c401a856219c945397f772eea5ca50c6eb825cbc9d1e1", + "variant": null }, "cpython-3.11.5-linux-x86_64-musl": { "name": "cpython", @@ -1929,7 +3662,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "fe09ecd87f69a724acf26ca508d7ead91a951abb2da18dfb98fe22c284454121" + "sha256": "fe09ecd87f69a724acf26ca508d7ead91a951abb2da18dfb98fe22c284454121", + "variant": null }, "cpython-3.11.5-windows-i686-none": { "name": "cpython", @@ -1941,7 +3675,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "936b624c2512a3a3370aae8adf603d6ae71ba8ebd39cc4714a13306891ea36f0" + "sha256": "936b624c2512a3a3370aae8adf603d6ae71ba8ebd39cc4714a13306891ea36f0", + "variant": null }, "cpython-3.11.5-windows-x86_64-none": { "name": "cpython", @@ -1953,7 +3688,86 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "00f002263efc8aea896bcfaaf906b1f4dab3e5cd3db53e2b69ab9a10ba220b97" + "sha256": "00f002263efc8aea896bcfaaf906b1f4dab3e5cd3db53e2b69ab9a10ba220b97", + "variant": null + }, + "cpython-3.11.5+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 5, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.5+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 5, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.5+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 5, + "prerelease": "", + "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": "1aee6a613385a6355bed61a9b12259a5ed16e871b5bdfe5c9fe98b46ee2bb05e", + "variant": "debug" + }, + "cpython-3.11.5+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 5, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.5+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 5, + "prerelease": "", + "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": "93ee095b53de5a74af18e612f55095fcf3118c3c0a87eb6344d8eaca396bfb2d", + "variant": "debug" + }, + "cpython-3.11.5+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "96c77d4b1cbb47ac5eca384d21d689995c46e6a86d487acb73c9210eed3c5614", + "variant": "debug" }, "cpython-3.11.4-darwin-aarch64-none": { "name": "cpython", @@ -1965,7 +3779,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "cb6d2948384a857321f2aa40fa67744cd9676a330f08b6dad7070bda0b6120a4" + "sha256": "cb6d2948384a857321f2aa40fa67744cd9676a330f08b6dad7070bda0b6120a4", + "variant": null }, "cpython-3.11.4-darwin-x86_64-none": { "name": "cpython", @@ -1977,7 +3792,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "47e1557d93a42585972772e82661047ca5f608293158acb2778dccf120eabb00" + "sha256": "47e1557d93a42585972772e82661047ca5f608293158acb2778dccf120eabb00", + "variant": null }, "cpython-3.11.4-linux-aarch64-gnu": { "name": "cpython", @@ -1989,7 +3805,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "2e84fc53f4e90e11963281c5c871f593abcb24fc796a50337fa516be99af02fb" + "sha256": "2e84fc53f4e90e11963281c5c871f593abcb24fc796a50337fa516be99af02fb", + "variant": null }, "cpython-3.11.4-linux-i686-gnu": { "name": "cpython", @@ -2001,7 +3818,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "abdccc6ec7093f49da99680f5899a96bff0b96fde8f5d73f7aac121e0d05fdd8" + "sha256": "abdccc6ec7093f49da99680f5899a96bff0b96fde8f5d73f7aac121e0d05fdd8", + "variant": null }, "cpython-3.11.4-linux-powerpc64le-gnu": { "name": "cpython", @@ -2013,7 +3831,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "df7b92ed9cec96b3bb658fb586be947722ecd8e420fb23cee13d2e90abcfcf25" + "sha256": "df7b92ed9cec96b3bb658fb586be947722ecd8e420fb23cee13d2e90abcfcf25", + "variant": null }, "cpython-3.11.4-linux-s390x-gnu": { "name": "cpython", @@ -2025,7 +3844,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "e477f0749161f9aa7887964f089d9460a539f6b4a8fdab5166f898210e1a87a4" + "sha256": "e477f0749161f9aa7887964f089d9460a539f6b4a8fdab5166f898210e1a87a4", + "variant": null }, "cpython-3.11.4-linux-x86_64-gnu": { "name": "cpython", @@ -2037,7 +3857,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "e26247302bc8e9083a43ce9e8dd94905b40d464745b1603041f7bc9a93c65d05" + "sha256": "e26247302bc8e9083a43ce9e8dd94905b40d464745b1603041f7bc9a93c65d05", + "variant": null }, "cpython-3.11.4-linux-x86_64-musl": { "name": "cpython", @@ -2049,7 +3870,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "1218ca44595aeaf34271508db64a2abc581c3ee1eb307c1b0537ea746922b806" + "sha256": "1218ca44595aeaf34271508db64a2abc581c3ee1eb307c1b0537ea746922b806", + "variant": null }, "cpython-3.11.4-windows-i686-none": { "name": "cpython", @@ -2061,7 +3883,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "e2f4b41c3d89c5ec735e2563d752856cb3c19a0aa712ec7ef341712bafa7e905" + "sha256": "e2f4b41c3d89c5ec735e2563d752856cb3c19a0aa712ec7ef341712bafa7e905", + "variant": null }, "cpython-3.11.4-windows-x86_64-none": { "name": "cpython", @@ -2073,7 +3896,86 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "878614c03ea38538ae2f758e36c85d2c0eb1eaaca86cd400ff8c76693ee0b3e1" + "sha256": "878614c03ea38538ae2f758e36c85d2c0eb1eaaca86cd400ff8c76693ee0b3e1", + "variant": null + }, + "cpython-3.11.4+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 4, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.4+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 4, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.4+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 4, + "prerelease": "", + "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": "b9f76fd226bfcbc6a8769934b17323ca3b563f1c24660582fcccfa6d0c7146af", + "variant": "debug" + }, + "cpython-3.11.4+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 4, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.4+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 4, + "prerelease": "", + "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": "1b5fdeb2dc56c30843e7350f1684178755fae91666a0a987e5eb39074c42a052", + "variant": "debug" + }, + "cpython-3.11.4+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "d5467468ddee2b779096c5c4c0dcc74065d35fb38fea6c1c6630e7c2c904b1b9", + "variant": "debug" }, "cpython-3.11.3-darwin-aarch64-none": { "name": "cpython", @@ -2085,7 +3987,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "09e412506a8d63edbb6901742b54da9aa7faf120b8dbdce56c57b303fc892c86" + "sha256": "09e412506a8d63edbb6901742b54da9aa7faf120b8dbdce56c57b303fc892c86", + "variant": null }, "cpython-3.11.3-darwin-x86_64-none": { "name": "cpython", @@ -2097,7 +4000,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "f710b8d60621308149c100d5175fec39274ed0b9c99645484fd93d1716ef4310" + "sha256": "f710b8d60621308149c100d5175fec39274ed0b9c99645484fd93d1716ef4310", + "variant": null }, "cpython-3.11.3-linux-aarch64-gnu": { "name": "cpython", @@ -2109,7 +4013,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "8190accbbbbcf7620f1ff6d668e4dd090c639665d11188ce864b62554d40e5ab" + "sha256": "8190accbbbbcf7620f1ff6d668e4dd090c639665d11188ce864b62554d40e5ab", + "variant": null }, "cpython-3.11.3-linux-i686-gnu": { "name": "cpython", @@ -2121,7 +4026,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "36ff6c5ebca8bf07181b774874233eb37835a62b39493f975869acc5010d839d" + "sha256": "36ff6c5ebca8bf07181b774874233eb37835a62b39493f975869acc5010d839d", + "variant": null }, "cpython-3.11.3-linux-powerpc64le-gnu": { "name": "cpython", @@ -2133,7 +4039,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "767d24f3570b35fedb945f5ac66224c8983f2d556ab83c5cfaa5f3666e9c212c" + "sha256": "767d24f3570b35fedb945f5ac66224c8983f2d556ab83c5cfaa5f3666e9c212c", + "variant": null }, "cpython-3.11.3-linux-x86_64-gnu": { "name": "cpython", @@ -2145,7 +4052,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "da50b87d1ec42b3cb577dfd22a3655e43a53150f4f98a4bfb40757c9d7839ab5" + "sha256": "da50b87d1ec42b3cb577dfd22a3655e43a53150f4f98a4bfb40757c9d7839ab5", + "variant": null }, "cpython-3.11.3-linux-x86_64-musl": { "name": "cpython", @@ -2157,7 +4065,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "82eed5ae1ca9e60ed9b9cac97e910927ffe2e80e91161c74b2d70e44d5227de0" + "sha256": "82eed5ae1ca9e60ed9b9cac97e910927ffe2e80e91161c74b2d70e44d5227de0", + "variant": null }, "cpython-3.11.3-windows-i686-none": { "name": "cpython", @@ -2169,7 +4078,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "a6751e6fa5c7c4d4748ed534a7f00ad7f858f62ce73d63d44dd907036ba53985" + "sha256": "a6751e6fa5c7c4d4748ed534a7f00ad7f858f62ce73d63d44dd907036ba53985", + "variant": null }, "cpython-3.11.3-windows-x86_64-none": { "name": "cpython", @@ -2181,7 +4091,73 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "24741066da6f35a7ff67bee65ce82eae870d84e1181843e64a7076d1571e95af" + "sha256": "24741066da6f35a7ff67bee65ce82eae870d84e1181843e64a7076d1571e95af", + "variant": null + }, + "cpython-3.11.3+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 3, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.3+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 3, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.3+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 3, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.3+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 3, + "prerelease": "", + "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": "4f1192179e1f62e69b8b45f7f699e6f0100fb0b8a39aad7a48472794d0c24bd4", + "variant": "debug" + }, + "cpython-3.11.3+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "b753e060ccfb783b369f1e375ff6cc7a38d864a00506ec2e01ca01ba1956abc6", + "variant": "debug" }, "cpython-3.11.1-darwin-aarch64-none": { "name": "cpython", @@ -2193,7 +4169,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "4918cdf1cab742a90f85318f88b8122aeaa2d04705803c7b6e78e81a3dd40f80" + "sha256": "4918cdf1cab742a90f85318f88b8122aeaa2d04705803c7b6e78e81a3dd40f80", + "variant": null }, "cpython-3.11.1-darwin-x86_64-none": { "name": "cpython", @@ -2205,7 +4182,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "20a4203d069dc9b710f70b09e7da2ce6f473d6b1110f9535fb6f4c469ed54733" + "sha256": "20a4203d069dc9b710f70b09e7da2ce6f473d6b1110f9535fb6f4c469ed54733", + "variant": null }, "cpython-3.11.1-linux-aarch64-gnu": { "name": "cpython", @@ -2217,7 +4195,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "debf15783bdcb5530504f533d33fda75a7b905cec5361ae8f33da5ba6599f8b4" + "sha256": "debf15783bdcb5530504f533d33fda75a7b905cec5361ae8f33da5ba6599f8b4", + "variant": null }, "cpython-3.11.1-linux-i686-gnu": { "name": "cpython", @@ -2229,7 +4208,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "8392230cf76c282cfeaf67dcbd2e0fac6da8cd3b3aead1250505c6ddd606caae" + "sha256": "8392230cf76c282cfeaf67dcbd2e0fac6da8cd3b3aead1250505c6ddd606caae", + "variant": null }, "cpython-3.11.1-linux-x86_64-gnu": { "name": "cpython", @@ -2241,7 +4221,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "02a551fefab3750effd0e156c25446547c238688a32fabde2995c941c03a6423" + "sha256": "02a551fefab3750effd0e156c25446547c238688a32fabde2995c941c03a6423", + "variant": null }, "cpython-3.11.1-linux-x86_64-musl": { "name": "cpython", @@ -2253,7 +4234,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "7f0425d3e9b2283aba205493e9fe431bc2c2d67cc369bc922825b827a1b06b82" + "sha256": "7f0425d3e9b2283aba205493e9fe431bc2c2d67cc369bc922825b827a1b06b82", + "variant": null }, "cpython-3.11.1-windows-i686-none": { "name": "cpython", @@ -2265,7 +4247,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "50b250dd261c3cca9ae8d96cb921e4ffbc64f778a198b6f8b8b0a338f77ae486" + "sha256": "50b250dd261c3cca9ae8d96cb921e4ffbc64f778a198b6f8b8b0a338f77ae486", + "variant": null }, "cpython-3.11.1-windows-x86_64-none": { "name": "cpython", @@ -2277,7 +4260,60 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "edc08979cb0666a597466176511529c049a6f0bba8adf70df441708f766de5bf" + "sha256": "edc08979cb0666a597466176511529c049a6f0bba8adf70df441708f766de5bf", + "variant": null + }, + "cpython-3.11.1+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 1, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.1+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 1, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.11.1+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 11, + "patch": 1, + "prerelease": "", + "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": "b5bf700afc77588d853832d10b74ba793811cbec41b02ebc2c39a8b9987aacdd", + "variant": "debug" + }, + "cpython-3.11.1+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 11, + "patch": 1, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "7773aab3d1cbddbd0c6095c931fe841a2c511369e21744097276d22f4bc05621", + "variant": "debug" }, "cpython-3.10.15-darwin-aarch64-none": { "name": "cpython", @@ -2288,8 +4324,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1f753073b2333b2977922387fe016255ebde0f8558c567ffb960b7f50477fd34" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "fa79bd909bfeb627ffe66a8b023153495ece659e5e3b2ff56268535024db851c", + "variant": null }, "cpython-3.10.15-darwin-x86_64-none": { "name": "cpython", @@ -2300,8 +4337,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "18dd6c0c219da1ec7daede9da923205594dbdc7f37a283873363b4634a5e3f8f" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0d952fa2342794523ea7beee6a58e79e62045d0f018314ce282e9f2f1427ee2c", + "variant": null }, "cpython-3.10.15-linux-aarch64-gnu": { "name": "cpython", @@ -2312,8 +4350,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2773766d4dbe106a61031e38fd844f64886de93b7ae4389e0af52f7dc77092c3" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6008b42df79a0c8a4efe3aa88c2aea1471116aa66881a8ed15f04d66438cb7f5", + "variant": null }, "cpython-3.10.15-linux-armv7-gnueabi": { "name": "cpython", @@ -2324,8 +4363,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "eea8a6f99588e5bae2ed9ba3686a0ee095aee46058876d990a8b4fc87309448b" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "38daa81e0cbdc199d69241c35855dd05709f8246484cfe66b84666e123abb7df", + "variant": null }, "cpython-3.10.15-linux-armv7-gnueabihf": { "name": "cpython", @@ -2336,8 +4376,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "feab385a00f1312f318097521263cb17a68d4ce9c8945cfb97c07db3965838ca" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "af28aab17dd897d14ae04955b19be3080fbaa6778a251943d268bc597ac39427", + "variant": null }, "cpython-3.10.15-linux-powerpc64le-gnu": { "name": "cpython", @@ -2348,8 +4389,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2f90e76f5080cbc221447a41e7ec7949318c62c0e8e78ca919ed2f56669d8261" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4b86196b928b51ef3a0d51aa1690236e3da4561e34254e2929c0fcd37b37a002", + "variant": null }, "cpython-3.10.15-linux-s390x-gnu": { "name": "cpython", @@ -2360,8 +4402,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "14f8ed8e42db2296723f3e64e30e24ce2e27fc36534dcb77f474c8edb1fde872" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fbac57f67ca8a684f0442ff73c511efc177850c48f508f23521a816eae34d75f", + "variant": null }, "cpython-3.10.15-linux-x86_64-gnu": { "name": "cpython", @@ -2372,8 +4415,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "db58da5c9888a0dd7036d76ae4babd62f26ba74530a528b684af6f4688b86583" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "25fb8e23cd3b82b748075a04fd18f3183cc7316c11d6f59eb4b0326843892600", + "variant": null }, "cpython-3.10.15-linux-x86_64-musl": { "name": "cpython", @@ -2384,8 +4428,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a4eec7b3cbf5f2292f2d5ef0181317430a2e053d811bf8547918c27ded589416" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a169bdcd98f62421062fb9066763495913f4a86ee88c7d36e51df86d5d3cbe62", + "variant": null }, "cpython-3.10.15-windows-i686-none": { "name": "cpython", @@ -2396,8 +4441,9 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "29ea71edc4899061a9c3d2fd85773c1ae5d353ea9cb39c3d6b3e4c2a9bf4fda0" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "976d1560a02f2b921668fafc76196c1ff1bb24ccaa76ed5567539fb6dab0aa5a", + "variant": null }, "cpython-3.10.15-windows-x86_64-none": { "name": "cpython", @@ -2408,8 +4454,100 @@ "minor": 10, "patch": 15, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "50a39a5a09dd8786b154b116dbdbf7d6292fdf63b1683f6006d43246c0b9aad9" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "45a95225c659f9b988f444d985df347140ecc71c0297c6857febf5ef440d689a", + "variant": null + }, + "cpython-3.10.15+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0dae051d0957f4100e8c37b91f2185971d4e9f032962dfb90ccd44764990c936", + "variant": "debug" + }, + "cpython-3.10.15+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 10, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "dbe7c36174239ff999c474789e7d99ff96d0bf88df33a0736045645482e33a4d", + "variant": "debug" + }, + "cpython-3.10.15+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 10, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "2523fb5d7cbe639bb211971b90371bee7f82730479410ccded11c2a093bc3fd8", + "variant": "debug" + }, + "cpython-3.10.15+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2a756864521794fff976d840913b8be4786cc412ddf1fdf4616f3e39c3429971", + "variant": "debug" + }, + "cpython-3.10.15+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "bf7090927a5f05fdfd6e06aa6993ed150b01ab6397550d97437bfaa42deccd7b", + "variant": "debug" + }, + "cpython-3.10.15+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "817fa16f1a2bda3435cf99743049fd0744ae7bf1ddb52913189b702bb74408dd", + "variant": "debug" + }, + "cpython-3.10.15+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "13db15f74f19efc646544ddf7c46df543d2d6a1c3d7fba493d8b64dd3a379d5c", + "variant": "debug" }, "cpython-3.10.14-darwin-aarch64-none": { "name": "cpython", @@ -2421,7 +4559,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "f7ca9bffbce433c8d445edd33a5424c405553d735efee65a2fc5d8bbb1c8e137" + "sha256": "f7ca9bffbce433c8d445edd33a5424c405553d735efee65a2fc5d8bbb1c8e137", + "variant": null }, "cpython-3.10.14-darwin-x86_64-none": { "name": "cpython", @@ -2433,7 +4572,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "4404f44ec69c0708d4d88e98f39c2c1fe3bd462dc6a958b60aaf63028550c485" + "sha256": "4404f44ec69c0708d4d88e98f39c2c1fe3bd462dc6a958b60aaf63028550c485", + "variant": null }, "cpython-3.10.14-linux-aarch64-gnu": { "name": "cpython", @@ -2445,7 +4585,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0ffe64c77cacda7e3afcb0d8ba271c59ca0a30dfda218da39a573b412bb4afd7" + "sha256": "0ffe64c77cacda7e3afcb0d8ba271c59ca0a30dfda218da39a573b412bb4afd7", + "variant": null }, "cpython-3.10.14-linux-armv7-gnueabi": { "name": "cpython", @@ -2457,7 +4598,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "451449f18a49e6ceecf9c1f70f4aee0d1552eff103c3db291319125238182c9d" + "sha256": "451449f18a49e6ceecf9c1f70f4aee0d1552eff103c3db291319125238182c9d", + "variant": null }, "cpython-3.10.14-linux-armv7-gnueabihf": { "name": "cpython", @@ -2469,7 +4611,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "7f215b85df78c568847329faeb2c5007c301741d9c4ccebbd935a3a2963197b5" + "sha256": "7f215b85df78c568847329faeb2c5007c301741d9c4ccebbd935a3a2963197b5", + "variant": null }, "cpython-3.10.14-linux-powerpc64le-gnu": { "name": "cpython", @@ -2481,7 +4624,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8b83fdd95cb864f8ebfa1a1dd7e700bb046b8283bfd0a3aa04f1ff259eaff99e" + "sha256": "8b83fdd95cb864f8ebfa1a1dd7e700bb046b8283bfd0a3aa04f1ff259eaff99e", + "variant": null }, "cpython-3.10.14-linux-s390x-gnu": { "name": "cpython", @@ -2493,7 +4637,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ff1c4f010b1c6f563c71fa30f68293168536e0ed65f7d470a7e8c73252d08653" + "sha256": "ff1c4f010b1c6f563c71fa30f68293168536e0ed65f7d470a7e8c73252d08653", + "variant": null }, "cpython-3.10.14-linux-x86_64-gnu": { "name": "cpython", @@ -2505,7 +4650,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "159c456bb4a3802bafbce065ff54b99ddb16422500d75c1315573ee3b673af17" + "sha256": "159c456bb4a3802bafbce065ff54b99ddb16422500d75c1315573ee3b673af17", + "variant": null }, "cpython-3.10.14-linux-x86_64-musl": { "name": "cpython", @@ -2517,7 +4663,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "8803a748f2197ec2360af6feebe9c936f4f6beabcae1db5557fdd98fc922982c" + "sha256": "8803a748f2197ec2360af6feebe9c936f4f6beabcae1db5557fdd98fc922982c", + "variant": null }, "cpython-3.10.14-windows-i686-none": { "name": "cpython", @@ -2529,7 +4676,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "a84742f13584fd39f4f4b0d9a5865621a3c88cad91b31f17f414186719063364" + "sha256": "a84742f13584fd39f4f4b0d9a5865621a3c88cad91b31f17f414186719063364", + "variant": null }, "cpython-3.10.14-windows-x86_64-none": { "name": "cpython", @@ -2541,7 +4689,99 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "61ad1abcaca639eecb5bd0b129ac0315d79f7b90cf0aca8e9fb85c9e7269c26b" + "sha256": "61ad1abcaca639eecb5bd0b129ac0315d79f7b90cf0aca8e9fb85c9e7269c26b", + "variant": null + }, + "cpython-3.10.14+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a5dad9640ce4af0091e347661effc6438573097cb85dabe6f7ae073266f5c261", + "variant": "debug" + }, + "cpython-3.10.14+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 10, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "c6a1c577f10bc6c938e7e64c8b2f60914f4193d8f602a3b89b398f4a06d674af", + "variant": "debug" + }, + "cpython-3.10.14+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 10, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "10ed35c173a7e6c482d1dcace5be2a5a7a8c937ebd92169020e9f401456551f0", + "variant": "debug" + }, + "cpython-3.10.14+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "85d3914679b7c0b5cc317ee4888e9d458a5afc985aae4d95c698d0fbf7a2b1c5", + "variant": "debug" + }, + "cpython-3.10.14+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9134680d4c4488a23c876188f066804625e64041ee9bd0e4e57a2d519f575eae", + "variant": "debug" + }, + "cpython-3.10.14+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "060bcd01e5ba9cbd3f5b8da98c16480320b19fa9f67cf9ecf94a0c20ba08db6e", + "variant": "debug" + }, + "cpython-3.10.14+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "cd33d23d8fb79ca99db1398a555daad44af4122d01fb4065fd79e121929c5cb3", + "variant": "debug" }, "cpython-3.10.13-darwin-aarch64-none": { "name": "cpython", @@ -2553,7 +4793,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "5fdc0f6a5b5a90fd3c528e8b1da8e3aac931ea8690126c2fdb4254c84a3ff04a" + "sha256": "5fdc0f6a5b5a90fd3c528e8b1da8e3aac931ea8690126c2fdb4254c84a3ff04a", + "variant": null }, "cpython-3.10.13-darwin-x86_64-none": { "name": "cpython", @@ -2565,7 +4806,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "6378dfd22f58bb553ddb02be28304d739cd730c1f95c15c74955c923a1bc3d6a" + "sha256": "6378dfd22f58bb553ddb02be28304d739cd730c1f95c15c74955c923a1bc3d6a", + "variant": null }, "cpython-3.10.13-linux-aarch64-gnu": { "name": "cpython", @@ -2577,7 +4819,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "a898a88705611b372297bb8fe4d23cc16b8603ce5f24494c3a8cfa65d83787f9" + "sha256": "a898a88705611b372297bb8fe4d23cc16b8603ce5f24494c3a8cfa65d83787f9", + "variant": null }, "cpython-3.10.13-linux-i686-gnu": { "name": "cpython", @@ -2589,7 +4832,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.10.13%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "424d239b6df60e40849ad18505de394001233ab3d7470b5280fec6e643208bb9" + "sha256": "424d239b6df60e40849ad18505de394001233ab3d7470b5280fec6e643208bb9", + "variant": null }, "cpython-3.10.13-linux-powerpc64le-gnu": { "name": "cpython", @@ -2601,7 +4845,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c23706e138a0351fc1e9def2974af7b8206bac7ecbbb98a78f5aa9e7535fee42" + "sha256": "c23706e138a0351fc1e9def2974af7b8206bac7ecbbb98a78f5aa9e7535fee42", + "variant": null }, "cpython-3.10.13-linux-s390x-gnu": { "name": "cpython", @@ -2613,7 +4858,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "09be8fb2cdfbb4a93d555f268f244dbe4d8ff1854b2658e8043aa4ec08aede3e" + "sha256": "09be8fb2cdfbb4a93d555f268f244dbe4d8ff1854b2658e8043aa4ec08aede3e", + "variant": null }, "cpython-3.10.13-linux-x86_64-gnu": { "name": "cpython", @@ -2625,7 +4871,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "d995d032ca702afd2fc3a689c1f84a6c64972ecd82bba76a61d525f08eb0e195" + "sha256": "d995d032ca702afd2fc3a689c1f84a6c64972ecd82bba76a61d525f08eb0e195", + "variant": null }, "cpython-3.10.13-linux-x86_64-musl": { "name": "cpython", @@ -2637,7 +4884,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "48365ea10aa1b0768a153bfff50d1515a757d42409b02a4af4db354803f2d180" + "sha256": "48365ea10aa1b0768a153bfff50d1515a757d42409b02a4af4db354803f2d180", + "variant": null }, "cpython-3.10.13-windows-i686-none": { "name": "cpython", @@ -2649,7 +4897,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "5365b90f9cba7186d12dd86516ece8b696db7311128e0b49c92234e01a74599f" + "sha256": "5365b90f9cba7186d12dd86516ece8b696db7311128e0b49c92234e01a74599f", + "variant": null }, "cpython-3.10.13-windows-x86_64-none": { "name": "cpython", @@ -2661,7 +4910,86 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "086f7fe9156b897bb401273db8359017104168ac36f60f3af4e31ac7acd6634e" + "sha256": "086f7fe9156b897bb401273db8359017104168ac36f60f3af4e31ac7acd6634e", + "variant": null + }, + "cpython-3.10.13+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.13+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.13+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.13+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.13+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.13+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 13, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "3eec53aef154273c0bc30bb9905734762171f474f73ba256c8883022915b7439", + "variant": "debug" }, "cpython-3.10.12-darwin-aarch64-none": { "name": "cpython", @@ -2673,7 +5001,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "bc66c706ea8c5fc891635fda8f9da971a1a901d41342f6798c20ad0b2a25d1d6" + "sha256": "bc66c706ea8c5fc891635fda8f9da971a1a901d41342f6798c20ad0b2a25d1d6", + "variant": null }, "cpython-3.10.12-darwin-x86_64-none": { "name": "cpython", @@ -2685,7 +5014,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "8a6e3ed973a671de468d9c691ed9cb2c3a4858c5defffcf0b08969fba9c1dd04" + "sha256": "8a6e3ed973a671de468d9c691ed9cb2c3a4858c5defffcf0b08969fba9c1dd04", + "variant": null }, "cpython-3.10.12-linux-aarch64-gnu": { "name": "cpython", @@ -2697,7 +5027,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "fee80e221663eca5174bd794cb5047e40d3910dbeadcdf1f09d405a4c1c15fe4" + "sha256": "fee80e221663eca5174bd794cb5047e40d3910dbeadcdf1f09d405a4c1c15fe4", + "variant": null }, "cpython-3.10.12-linux-i686-gnu": { "name": "cpython", @@ -2709,7 +5040,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c7a5321a696ef6467791312368a04d36828907a8f5c557b96067fa534c716c18" + "sha256": "c7a5321a696ef6467791312368a04d36828907a8f5c557b96067fa534c716c18", + "variant": null }, "cpython-3.10.12-linux-powerpc64le-gnu": { "name": "cpython", @@ -2721,7 +5053,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "bb5e8cb0d2e44241725fa9b342238245503e7849917660006b0246a9c97b1d6c" + "sha256": "bb5e8cb0d2e44241725fa9b342238245503e7849917660006b0246a9c97b1d6c", + "variant": null }, "cpython-3.10.12-linux-s390x-gnu": { "name": "cpython", @@ -2733,7 +5066,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "8d33d435ae6fb93ded7fc26798cc0a1a4f546a4e527012a1e2909cc314b332df" + "sha256": "8d33d435ae6fb93ded7fc26798cc0a1a4f546a4e527012a1e2909cc314b332df", + "variant": null }, "cpython-3.10.12-linux-x86_64-gnu": { "name": "cpython", @@ -2745,7 +5079,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "a476dbca9184df9fc69fe6309cda5ebaf031d27ca9e529852437c94ec1bc43d3" + "sha256": "a476dbca9184df9fc69fe6309cda5ebaf031d27ca9e529852437c94ec1bc43d3", + "variant": null }, "cpython-3.10.12-linux-x86_64-musl": { "name": "cpython", @@ -2757,7 +5092,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "9080014bee2d4bd1f96bcbebf447d40c35ae9354382246add1160bd0d433ebf7" + "sha256": "9080014bee2d4bd1f96bcbebf447d40c35ae9354382246add1160bd0d433ebf7", + "variant": null }, "cpython-3.10.12-windows-i686-none": { "name": "cpython", @@ -2769,7 +5105,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "a5a5f9c9082b6503462a6b134111d3c303052cbc49ff31fff2ade38b39978e5d" + "sha256": "a5a5f9c9082b6503462a6b134111d3c303052cbc49ff31fff2ade38b39978e5d", + "variant": null }, "cpython-3.10.12-windows-x86_64-none": { "name": "cpython", @@ -2781,7 +5118,86 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "c1a31c353ca44de7d1b1a3b6c55a823e9c1eed0423d4f9f66e617bdb1b608685" + "sha256": "c1a31c353ca44de7d1b1a3b6c55a823e9c1eed0423d4f9f66e617bdb1b608685", + "variant": null + }, + "cpython-3.10.12+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 12, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.12+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 12, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.12+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 12, + "prerelease": "", + "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": "c318050fa91d84d447f5c8a5887a44f1cc8dd34d4c1d357cd755407d46ed1b21", + "variant": "debug" + }, + "cpython-3.10.12+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 12, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.12+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 12, + "prerelease": "", + "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": "fb7354fcee7b17dd0793ebd3f6f1fc8b7b205332afcf8d700cc1119f2dc33ff7", + "variant": "debug" + }, + "cpython-3.10.12+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 12, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ebff76754ae37694581afe80749efb1260a6da95a9d88f8e60aa2cab75fd5497", + "variant": "debug" }, "cpython-3.10.11-darwin-aarch64-none": { "name": "cpython", @@ -2793,7 +5209,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "8348bc3c2311f94ec63751fb71bd0108174be1c4def002773cf519ee1506f96f" + "sha256": "8348bc3c2311f94ec63751fb71bd0108174be1c4def002773cf519ee1506f96f", + "variant": null }, "cpython-3.10.11-darwin-x86_64-none": { "name": "cpython", @@ -2805,7 +5222,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "bd3fc6e4da6f4033ebf19d66704e73b0804c22641ddae10bbe347c48f82374ad" + "sha256": "bd3fc6e4da6f4033ebf19d66704e73b0804c22641ddae10bbe347c48f82374ad", + "variant": null }, "cpython-3.10.11-linux-aarch64-gnu": { "name": "cpython", @@ -2817,7 +5235,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c7573fdb00239f86b22ea0e8e926ca881d24fde5e5890851339911d76110bc35" + "sha256": "c7573fdb00239f86b22ea0e8e926ca881d24fde5e5890851339911d76110bc35", + "variant": null }, "cpython-3.10.11-linux-i686-gnu": { "name": "cpython", @@ -2829,7 +5248,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c70518620e32b074b1b40579012f0c67191a967e43e84b8f46052b6b893f7eeb" + "sha256": "c70518620e32b074b1b40579012f0c67191a967e43e84b8f46052b6b893f7eeb", + "variant": null }, "cpython-3.10.11-linux-powerpc64le-gnu": { "name": "cpython", @@ -2841,7 +5261,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "73a9d4c89ed51be39dd2de4e235078281087283e9fdedef65bec02f503e906ee" + "sha256": "73a9d4c89ed51be39dd2de4e235078281087283e9fdedef65bec02f503e906ee", + "variant": null }, "cpython-3.10.11-linux-x86_64-gnu": { "name": "cpython", @@ -2853,7 +5274,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c5bcaac91bc80bfc29cf510669ecad12d506035ecb3ad85ef213416d54aecd79" + "sha256": "c5bcaac91bc80bfc29cf510669ecad12d506035ecb3ad85ef213416d54aecd79", + "variant": null }, "cpython-3.10.11-linux-x86_64-musl": { "name": "cpython", @@ -2865,7 +5287,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "c5dde3276541a8ad000ba631ec70012aa2261926c13f54d2b1de83dad61d59c1" + "sha256": "c5dde3276541a8ad000ba631ec70012aa2261926c13f54d2b1de83dad61d59c1", + "variant": null }, "cpython-3.10.11-windows-i686-none": { "name": "cpython", @@ -2877,7 +5300,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "e4ed3414cd0e687017f0a56fed88ff39b3f5dfb24a0d62e9c7ca55854178bcde" + "sha256": "e4ed3414cd0e687017f0a56fed88ff39b3f5dfb24a0d62e9c7ca55854178bcde", + "variant": null }, "cpython-3.10.11-windows-x86_64-none": { "name": "cpython", @@ -2889,7 +5313,73 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "9c2d3604a06fcd422289df73015cd00e7271d90de28d2c910f0e2309a7f73a68" + "sha256": "9c2d3604a06fcd422289df73015cd00e7271d90de28d2c910f0e2309a7f73a68", + "variant": null + }, + "cpython-3.10.11+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 11, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.11+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 11, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.11+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 11, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.11+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 11, + "prerelease": "", + "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": "544e5020f71ad1525dbc92b08e429cc1e1e11866c48c07d91e99f531b9ba68b0", + "variant": "debug" + }, + "cpython-3.10.11+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 11, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "0d5bd092b85ada04f6f27a5ef30e026ec2df8ddc73f89d7d1d397623405011c1", + "variant": "debug" }, "cpython-3.10.9-darwin-aarch64-none": { "name": "cpython", @@ -2901,7 +5391,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "018d05a779b2de7a476f3b3ff2d10f503d69d14efcedd0774e6dab8c22ef84ff" + "sha256": "018d05a779b2de7a476f3b3ff2d10f503d69d14efcedd0774e6dab8c22ef84ff", + "variant": null }, "cpython-3.10.9-darwin-x86_64-none": { "name": "cpython", @@ -2913,7 +5404,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "0e685f98dce0e5bc8da93c7081f4e6c10219792e223e4b5886730fd73a7ba4c6" + "sha256": "0e685f98dce0e5bc8da93c7081f4e6c10219792e223e4b5886730fd73a7ba4c6", + "variant": null }, "cpython-3.10.9-linux-aarch64-gnu": { "name": "cpython", @@ -2925,7 +5417,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "2003750f40cd09d4bf7a850342613992f8d9454f03b3c067989911fb37e7a4d1" + "sha256": "2003750f40cd09d4bf7a850342613992f8d9454f03b3c067989911fb37e7a4d1", + "variant": null }, "cpython-3.10.9-linux-i686-gnu": { "name": "cpython", @@ -2937,7 +5430,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "44566c08eb8054aa0784f76b85d2c6c70a62f4988d5e9abcce819b517b329fdd" + "sha256": "44566c08eb8054aa0784f76b85d2c6c70a62f4988d5e9abcce819b517b329fdd", + "variant": null }, "cpython-3.10.9-linux-x86_64-gnu": { "name": "cpython", @@ -2949,7 +5443,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "d196347aeb701a53fe2bb2b095abec38d27d0fa0443f8a1c2023a1bed6e18cdf" + "sha256": "d196347aeb701a53fe2bb2b095abec38d27d0fa0443f8a1c2023a1bed6e18cdf", + "variant": null }, "cpython-3.10.9-linux-x86_64-musl": { "name": "cpython", @@ -2961,7 +5456,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "cf17e6d042777170e423c6b80e096ad8273d9848708875db0d23dd45bdb3d516" + "sha256": "cf17e6d042777170e423c6b80e096ad8273d9848708875db0d23dd45bdb3d516", + "variant": null }, "cpython-3.10.9-windows-i686-none": { "name": "cpython", @@ -2973,7 +5469,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "c5c51d9a3e8d8cdac67d8f3ad7c4008de169ff1480e17021f154d5c99fcee9e3" + "sha256": "c5c51d9a3e8d8cdac67d8f3ad7c4008de169ff1480e17021f154d5c99fcee9e3", + "variant": null }, "cpython-3.10.9-windows-x86_64-none": { "name": "cpython", @@ -2985,7 +5482,60 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "59c6970cecb357dc1d8554bd0540eb81ee7f6d16a07acf3d14ed294ece02c035" + "sha256": "59c6970cecb357dc1d8554bd0540eb81ee7f6d16a07acf3d14ed294ece02c035", + "variant": null + }, + "cpython-3.10.9+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 9, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.9+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 9, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.9+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 9, + "prerelease": "", + "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": "a90a45ba7afcbd1df9aef96a614acbb210607299ac74dadbb6bd66af22be34db", + "variant": "debug" + }, + "cpython-3.10.9+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "7e0a0094b580d285163ede7797945e86bd4905d6af3340e6554e6abba7bcb832", + "variant": "debug" }, "cpython-3.10.8-darwin-aarch64-none": { "name": "cpython", @@ -2997,7 +5547,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "d52b03817bd245d28e0a8b2f715716cd0fcd112820ccff745636932c76afa20a" + "sha256": "d52b03817bd245d28e0a8b2f715716cd0fcd112820ccff745636932c76afa20a", + "variant": null }, "cpython-3.10.8-darwin-x86_64-none": { "name": "cpython", @@ -3009,7 +5560,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "525b79c7ce5de90ab66bd07b0ac1008bafa147ddc8a41bef15ffb7c9c1e9e7c5" + "sha256": "525b79c7ce5de90ab66bd07b0ac1008bafa147ddc8a41bef15ffb7c9c1e9e7c5", + "variant": null }, "cpython-3.10.8-linux-aarch64-gnu": { "name": "cpython", @@ -3021,7 +5573,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "33170bef18c811906b738be530f934640491b065bf16c4d276c6515321918132" + "sha256": "33170bef18c811906b738be530f934640491b065bf16c4d276c6515321918132", + "variant": null }, "cpython-3.10.8-linux-i686-gnu": { "name": "cpython", @@ -3033,7 +5586,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "2deee7cbbd5dad339d713a75ec92239725d2035e833af5b9981b026dee0b9213" + "sha256": "2deee7cbbd5dad339d713a75ec92239725d2035e833af5b9981b026dee0b9213", + "variant": null }, "cpython-3.10.8-linux-x86_64-gnu": { "name": "cpython", @@ -3045,7 +5599,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "6c8db44ae0e18e320320bbaaafd2d69cde8bfea171ae2d651b7993d1396260b7" + "sha256": "6c8db44ae0e18e320320bbaaafd2d69cde8bfea171ae2d651b7993d1396260b7", + "variant": null }, "cpython-3.10.8-linux-x86_64-musl": { "name": "cpython", @@ -3057,7 +5612,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "9f035bbe53f55fb406f95cb68459ba245b386084eeb5760f1660f416b730328d" + "sha256": "9f035bbe53f55fb406f95cb68459ba245b386084eeb5760f1660f416b730328d", + "variant": null }, "cpython-3.10.8-windows-i686-none": { "name": "cpython", @@ -3069,7 +5625,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "94e76273166f72624128e52b5402db244cea041dab4a6bcdc70b304b66e27e95" + "sha256": "94e76273166f72624128e52b5402db244cea041dab4a6bcdc70b304b66e27e95", + "variant": null }, "cpython-3.10.8-windows-x86_64-none": { "name": "cpython", @@ -3081,7 +5638,60 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "f2b6d2f77118f06dd2ca04dae1175e44aaa5077a5ed8ddc63333c15347182bfe" + "sha256": "f2b6d2f77118f06dd2ca04dae1175e44aaa5077a5ed8ddc63333c15347182bfe", + "variant": null + }, + "cpython-3.10.8+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 8, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.8+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 8, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.8+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 8, + "prerelease": "", + "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": "c86182951a82e761588476a0155afe99ae4ae1030e4a8e1e8bcb8e1d42f6327c", + "variant": "debug" + }, + "cpython-3.10.8+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 8, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "935eb97e0c3ef358a2f25e78b0b56aebad68d371e3b63979a157c7588a03585b", + "variant": "debug" }, "cpython-3.10.7-darwin-aarch64-none": { "name": "cpython", @@ -3093,7 +5703,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "70f6ca1da8e6fce832ad0b7f9fdaba0b84ba0ac0a4c626127acb6d49df4b8f91" + "sha256": "70f6ca1da8e6fce832ad0b7f9fdaba0b84ba0ac0a4c626127acb6d49df4b8f91", + "variant": null }, "cpython-3.10.7-darwin-x86_64-none": { "name": "cpython", @@ -3105,7 +5716,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "6101f580434544d28d5590543029a7c6bdf07efa4bcdb5e4cbedb3cd83241922" + "sha256": "6101f580434544d28d5590543029a7c6bdf07efa4bcdb5e4cbedb3cd83241922", + "variant": null }, "cpython-3.10.7-linux-aarch64-gnu": { "name": "cpython", @@ -3117,7 +5729,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "dfeec186a62a6068259d90e8d77e7d30eaf9c2b4ae7b205ff8caab7cb21f277c" + "sha256": "dfeec186a62a6068259d90e8d77e7d30eaf9c2b4ae7b205ff8caab7cb21f277c", + "variant": null }, "cpython-3.10.7-linux-i686-gnu": { "name": "cpython", @@ -3129,7 +5742,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "4a611ce990dc1f32bc4b35d276f04521464127f77e1133ac5bb9c6ba23e94a82" + "sha256": "4a611ce990dc1f32bc4b35d276f04521464127f77e1133ac5bb9c6ba23e94a82", + "variant": null }, "cpython-3.10.7-linux-x86_64-gnu": { "name": "cpython", @@ -3141,7 +5755,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c12c9ad2b2c75464541d897c0528013adecd8be5b30acf4411f7759729841711" + "sha256": "c12c9ad2b2c75464541d897c0528013adecd8be5b30acf4411f7759729841711", + "variant": null }, "cpython-3.10.7-linux-x86_64-musl": { "name": "cpython", @@ -3153,7 +5768,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "3e0cab6e49ad5ef95851049463797ec713eee6e1f2fa1d99e30516d37797c3f0" + "sha256": "3e0cab6e49ad5ef95851049463797ec713eee6e1f2fa1d99e30516d37797c3f0", + "variant": null }, "cpython-3.10.7-windows-i686-none": { "name": "cpython", @@ -3165,7 +5781,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "384e711dd657c3439be4e50b2485478a7ed7a259a741d4480fc96d82cc09d318" + "sha256": "384e711dd657c3439be4e50b2485478a7ed7a259a741d4480fc96d82cc09d318", + "variant": null }, "cpython-3.10.7-windows-x86_64-none": { "name": "cpython", @@ -3177,7 +5794,60 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "b464352f8cbf06ab4c041b7559c9bda7e9f6001a94f67ab0a342cba078f3805f" + "sha256": "b464352f8cbf06ab4c041b7559c9bda7e9f6001a94f67ab0a342cba078f3805f", + "variant": null + }, + "cpython-3.10.7+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 7, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.7+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 7, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.7+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 7, + "prerelease": "", + "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": "ce3fe27e6ca3a0e75a7f4f3b6568cd1bf967230a67e73393e94a23380dddaf10", + "variant": "debug" + }, + "cpython-3.10.7+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "08f725cdb4d4f6bd76c582b798f7d7685c8f5c5afa03e1553e28e55a3859014e", + "variant": "debug" }, "cpython-3.10.6-darwin-aarch64-none": { "name": "cpython", @@ -3189,7 +5859,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "efaf66acdb9a4eb33d57702607d2e667b1a319d58c167a43c96896b97419b8b7" + "sha256": "efaf66acdb9a4eb33d57702607d2e667b1a319d58c167a43c96896b97419b8b7", + "variant": null }, "cpython-3.10.6-darwin-x86_64-none": { "name": "cpython", @@ -3201,7 +5872,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "7718411adf3ea1480f3f018a643eb0550282aefe39e5ecb3f363a4a566a9398c" + "sha256": "7718411adf3ea1480f3f018a643eb0550282aefe39e5ecb3f363a4a566a9398c", + "variant": null }, "cpython-3.10.6-linux-aarch64-gnu": { "name": "cpython", @@ -3213,7 +5885,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "81625f5c97f61e2e3d7e9f62c484b1aa5311f21bd6545451714b949a29da5435" + "sha256": "81625f5c97f61e2e3d7e9f62c484b1aa5311f21bd6545451714b949a29da5435", + "variant": null }, "cpython-3.10.6-linux-i686-gnu": { "name": "cpython", @@ -3225,7 +5898,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "b152801a2609e6a38f3cc9e7e21d8b6cf5b6f31dacfcaca01e162c514e851ed6" + "sha256": "b152801a2609e6a38f3cc9e7e21d8b6cf5b6f31dacfcaca01e162c514e851ed6", + "variant": null }, "cpython-3.10.6-linux-x86_64-gnu": { "name": "cpython", @@ -3237,7 +5911,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "55aa2190d28dcfdf414d96dc5dcea9fe048fadcd583dc3981fec020869826111" + "sha256": "55aa2190d28dcfdf414d96dc5dcea9fe048fadcd583dc3981fec020869826111", + "variant": null }, "cpython-3.10.6-linux-x86_64-musl": { "name": "cpython", @@ -3249,7 +5924,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "8cafe6409e9d192b288b84a21bc0c309f1d3f6b809a471b2858c7bf1bb09f3a7" + "sha256": "8cafe6409e9d192b288b84a21bc0c309f1d3f6b809a471b2858c7bf1bb09f3a7", + "variant": null }, "cpython-3.10.6-windows-i686-none": { "name": "cpython", @@ -3261,7 +5937,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "27f22babf29ceebae18b2c2e38e2c48d22de686688c8a31c5f8d7d51541583c1" + "sha256": "27f22babf29ceebae18b2c2e38e2c48d22de686688c8a31c5f8d7d51541583c1", + "variant": null }, "cpython-3.10.6-windows-x86_64-none": { "name": "cpython", @@ -3273,7 +5950,60 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "91889a7dbdceea585ff4d3b7856a6bb8f8a4eca83a0ff52a73542c2e67220eaa" + "sha256": "91889a7dbdceea585ff4d3b7856a6bb8f8a4eca83a0ff52a73542c2e67220eaa", + "variant": null + }, + "cpython-3.10.6+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 6, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.6+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 6, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.6+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 6, + "prerelease": "", + "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": "407e5951e39f5652b32b72b715c4aa772dd8c2da1065161c58c30a1f976dd1b2", + "variant": "debug" + }, + "cpython-3.10.6+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6cd58957e286c132dc7cdbd937144aa180b557772be8a2b70bd1c3f2644ebe65", + "variant": "debug" }, "cpython-3.10.5-darwin-aarch64-none": { "name": "cpython", @@ -3285,7 +6015,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "19d1aa4a6d9ddb0094fc36961b129de9abe1673bce66c86cd97b582795c496a8" + "sha256": "19d1aa4a6d9ddb0094fc36961b129de9abe1673bce66c86cd97b582795c496a8", + "variant": null }, "cpython-3.10.5-darwin-x86_64-none": { "name": "cpython", @@ -3297,7 +6028,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "eca0584397d9a3ef6f7bb32b0476318b01c89b7b0a031ef97a0dcaa5ba5127a8" + "sha256": "eca0584397d9a3ef6f7bb32b0476318b01c89b7b0a031ef97a0dcaa5ba5127a8", + "variant": null }, "cpython-3.10.5-linux-aarch64-gnu": { "name": "cpython", @@ -3309,7 +6041,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "012fa37c12d2647d76d004dc003302563864d2f1cd0731b71eeafad63d28b3f0" + "sha256": "012fa37c12d2647d76d004dc003302563864d2f1cd0731b71eeafad63d28b3f0", + "variant": null }, "cpython-3.10.5-linux-i686-gnu": { "name": "cpython", @@ -3321,7 +6054,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "5abf5baf40f8573ce7d7e4ad323457f511833e1663e61ac5a11d5563a735159f" + "sha256": "5abf5baf40f8573ce7d7e4ad323457f511833e1663e61ac5a11d5563a735159f", + "variant": null }, "cpython-3.10.5-linux-x86_64-gnu": { "name": "cpython", @@ -3333,7 +6067,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "460f87a389be28c953c24c6f942f172f9ce7f331367b4daf89cb450baedd51d7" + "sha256": "460f87a389be28c953c24c6f942f172f9ce7f331367b4daf89cb450baedd51d7", + "variant": null }, "cpython-3.10.5-linux-x86_64-musl": { "name": "cpython", @@ -3345,7 +6080,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "6aad42c7b03989173dd0e4d066e8c1e9f176f4b31d5bde26dbb5297f38f656d0" + "sha256": "6aad42c7b03989173dd0e4d066e8c1e9f176f4b31d5bde26dbb5297f38f656d0", + "variant": null }, "cpython-3.10.5-windows-i686-none": { "name": "cpython", @@ -3357,7 +6093,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "2846e9c7e8484034989ab218022009fdd9dcb12a7bfb4b0329a404552d37e9aa" + "sha256": "2846e9c7e8484034989ab218022009fdd9dcb12a7bfb4b0329a404552d37e9aa", + "variant": null }, "cpython-3.10.5-windows-x86_64-none": { "name": "cpython", @@ -3369,7 +6106,60 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "c830ab2a3a488f9cf95e4e81c581d9ef73e483c2e6546136379443e9bb725119" + "sha256": "c830ab2a3a488f9cf95e4e81c581d9ef73e483c2e6546136379443e9bb725119", + "variant": null + }, + "cpython-3.10.5+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 5, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.5+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 5, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.5+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 5, + "prerelease": "", + "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": "f8dfb83885d1cbc82febfa613258c1f6954ea88ef43ed7dc710d6df20efecdab", + "variant": "debug" + }, + "cpython-3.10.5+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5217476a38b5273fd98ce45b7f0efcd579b8740c4d2911c6900fa16d59368fcc", + "variant": "debug" }, "cpython-3.10.4-darwin-aarch64-none": { "name": "cpython", @@ -3381,7 +6171,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "6d2e4e6b1c403bce84cfb846400754017f525fe8017f186e8e7072fcaaf3aa71" + "sha256": "6d2e4e6b1c403bce84cfb846400754017f525fe8017f186e8e7072fcaaf3aa71", + "variant": null }, "cpython-3.10.4-darwin-x86_64-none": { "name": "cpython", @@ -3393,7 +6184,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "c4a57a13b084d49ce8c2eb5b2662ee45b0c55b08ddd696f473233b0787f03988" + "sha256": "c4a57a13b084d49ce8c2eb5b2662ee45b0c55b08ddd696f473233b0787f03988", + "variant": null }, "cpython-3.10.4-linux-aarch64-gnu": { "name": "cpython", @@ -3405,7 +6197,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "7a8989392dc9b41d85959a752448c60852cf0061de565e98445c27f6bbdf63be" + "sha256": "7a8989392dc9b41d85959a752448c60852cf0061de565e98445c27f6bbdf63be", + "variant": null }, "cpython-3.10.4-linux-i686-gnu": { "name": "cpython", @@ -3417,7 +6210,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "f3bc0828a0e0a8974e3fe90b4e99549296a7578de2321d791be1bad28191921d" + "sha256": "f3bc0828a0e0a8974e3fe90b4e99549296a7578de2321d791be1bad28191921d", + "variant": null }, "cpython-3.10.4-linux-x86_64-gnu": { "name": "cpython", @@ -3429,7 +6223,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "1f8423808ad84c0e56c8e14c32685cbfbc1159e0d9f943ac946f29e84cf1b5ee" + "sha256": "1f8423808ad84c0e56c8e14c32685cbfbc1159e0d9f943ac946f29e84cf1b5ee", + "variant": null }, "cpython-3.10.4-linux-x86_64-musl": { "name": "cpython", @@ -3441,7 +6236,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "74c8da0aa24233c76bdd984d3c9e44442eca316be8a2cb4972d9264fedb0d5e8" + "sha256": "74c8da0aa24233c76bdd984d3c9e44442eca316be8a2cb4972d9264fedb0d5e8", + "variant": null }, "cpython-3.10.4-windows-i686-none": { "name": "cpython", @@ -3453,7 +6249,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "e1dfa5dde910f908cad8bd688b29d28df832f7b150555679c204580d1af0c4a6" + "sha256": "e1dfa5dde910f908cad8bd688b29d28df832f7b150555679c204580d1af0c4a6", + "variant": null }, "cpython-3.10.4-windows-x86_64-none": { "name": "cpython", @@ -3465,7 +6262,60 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "7231ba2af9525cae620a5f4ae3bf89a939fdc053ba0cc64ee3dead8f13188005" + "sha256": "7231ba2af9525cae620a5f4ae3bf89a939fdc053ba0cc64ee3dead8f13188005", + "variant": null + }, + "cpython-3.10.4+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 4, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.4+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 4, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.4+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 4, + "prerelease": "", + "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": "7699f76ef89b436b452eacdbab508da3cd94146ba29b099f5cb6e250afba3210", + "variant": "debug" + }, + "cpython-3.10.4+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "8fb78fbf9266b23ee0eaf569f7a36d1696f7102c396106c1d71b3a991b27ad27", + "variant": "debug" }, "cpython-3.10.3-darwin-aarch64-none": { "name": "cpython", @@ -3477,7 +6327,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "db46dadfccc407aa1f66ed607eefbf12f781e343adcb1edee0a3883d081292ce" + "sha256": "db46dadfccc407aa1f66ed607eefbf12f781e343adcb1edee0a3883d081292ce", + "variant": null }, "cpython-3.10.3-darwin-x86_64-none": { "name": "cpython", @@ -3489,7 +6340,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "ec2e90b6a589db7ef9f74358b1436558167629f9e4d725c8150496f9cb08a9d4" + "sha256": "ec2e90b6a589db7ef9f74358b1436558167629f9e4d725c8150496f9cb08a9d4", + "variant": null }, "cpython-3.10.3-linux-aarch64-gnu": { "name": "cpython", @@ -3501,7 +6353,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "f52ee68c13c4f9356eb78a5305d3178af2cb90c38a8ce8ce9990a7cf6ff06144" + "sha256": "f52ee68c13c4f9356eb78a5305d3178af2cb90c38a8ce8ce9990a7cf6ff06144", + "variant": null }, "cpython-3.10.3-linux-i686-gnu": { "name": "cpython", @@ -3513,7 +6366,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "2f125a927c3af52ef89af11857df988a042e26ce095129701b915e75b2ec6bff" + "sha256": "2f125a927c3af52ef89af11857df988a042e26ce095129701b915e75b2ec6bff", + "variant": null }, "cpython-3.10.3-linux-x86_64-gnu": { "name": "cpython", @@ -3525,7 +6379,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "b9989411bed71ba4867538c991f20b55f549dd9131905733f0df9f3fde81ad1d" + "sha256": "b9989411bed71ba4867538c991f20b55f549dd9131905733f0df9f3fde81ad1d", + "variant": null }, "cpython-3.10.3-linux-x86_64-musl": { "name": "cpython", @@ -3537,7 +6392,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "a60b589176879bdd465659660b87e954f969bed072c03c578ec828d6134f4ae1" + "sha256": "a60b589176879bdd465659660b87e954f969bed072c03c578ec828d6134f4ae1", + "variant": null }, "cpython-3.10.3-windows-i686-none": { "name": "cpython", @@ -3549,7 +6405,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "bb7f2a5143010fa482c5b442cced85516696cfc416ca92c903ef374532401a33" + "sha256": "bb7f2a5143010fa482c5b442cced85516696cfc416ca92c903ef374532401a33", + "variant": null }, "cpython-3.10.3-windows-x86_64-none": { "name": "cpython", @@ -3561,7 +6418,60 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "ba593370742ed8a7bc70ce563dd6a53e30ece1f6881e3888d334c1b485b0d9d0" + "sha256": "ba593370742ed8a7bc70ce563dd6a53e30ece1f6881e3888d334c1b485b0d9d0", + "variant": null + }, + "cpython-3.10.3+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 3, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.3+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 3, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.3+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 3, + "prerelease": "", + "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": "04760d869234ee8f801feb08edc042a6965320f6c0a7aedf92ec35501fef3b21", + "variant": "debug" + }, + "cpython-3.10.3+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "47637777bc44c6476e499bfcb214959c7abc386879dd66683a0d8e1b714c07cf", + "variant": "debug" }, "cpython-3.10.2-darwin-aarch64-none": { "name": "cpython", @@ -3573,7 +6483,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "1409acd9a506e2d1d3b65c1488db4e40d8f19d09a7df099667c87a506f71c0ef" + "sha256": "1409acd9a506e2d1d3b65c1488db4e40d8f19d09a7df099667c87a506f71c0ef", + "variant": null }, "cpython-3.10.2-darwin-x86_64-none": { "name": "cpython", @@ -3585,7 +6496,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "8146ad4390710ec69b316a5649912df0247d35f4a42e2aa9615bffd87b3e235a" + "sha256": "8146ad4390710ec69b316a5649912df0247d35f4a42e2aa9615bffd87b3e235a", + "variant": null }, "cpython-3.10.2-linux-aarch64-gnu": { "name": "cpython", @@ -3597,7 +6509,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "8f351a8cc348bb45c0f95b8634c8345ec6e749e483384188ad865b7428342703" + "sha256": "8f351a8cc348bb45c0f95b8634c8345ec6e749e483384188ad865b7428342703", + "variant": null }, "cpython-3.10.2-linux-i686-gnu": { "name": "cpython", @@ -3609,7 +6522,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "4fa49dab83bf82409816db431806525ce894280a509ca96c91e3efc9beed1fea" + "sha256": "4fa49dab83bf82409816db431806525ce894280a509ca96c91e3efc9beed1fea", + "variant": null }, "cpython-3.10.2-linux-x86_64-gnu": { "name": "cpython", @@ -3621,7 +6535,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "9b64eca2a94f7aff9409ad70bdaa7fbbf8148692662e764401883957943620dd" + "sha256": "9b64eca2a94f7aff9409ad70bdaa7fbbf8148692662e764401883957943620dd", + "variant": null }, "cpython-3.10.2-linux-x86_64-musl": { "name": "cpython", @@ -3633,7 +6548,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "c4f398f6f7f9bbf0df98407ad66bc5760f3afc2cd8ba33a99cf4dcc8c90fd9ae" + "sha256": "c4f398f6f7f9bbf0df98407ad66bc5760f3afc2cd8ba33a99cf4dcc8c90fd9ae", + "variant": null }, "cpython-3.10.2-windows-i686-none": { "name": "cpython", @@ -3645,7 +6561,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "5321f8c2c71239b1e2002d284be8ec825d4a6f95cd921e58db71f259834b7aa1" + "sha256": "5321f8c2c71239b1e2002d284be8ec825d4a6f95cd921e58db71f259834b7aa1", + "variant": null }, "cpython-3.10.2-windows-x86_64-none": { "name": "cpython", @@ -3657,7 +6574,86 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "a1d9a594cd3103baa24937ad9150c1a389544b4350e859200b3e5c036ac352bd" + "sha256": "a1d9a594cd3103baa24937ad9150c1a389544b4350e859200b3e5c036ac352bd", + "variant": null + }, + "cpython-3.10.2+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220222/cpython-3.10.2-aarch64-apple-darwin-debug-20220220T1113.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.10.2+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220222/cpython-3.10.2-x86_64-apple-darwin-debug-20220220T1113.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.10.2+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 2, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.2+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 2, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.10.2+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 2, + "prerelease": "", + "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": "d22d85f60b2ef982b747adda2d1bde4a32c23c3d8f652c00ce44526750859e4e", + "variant": "debug" + }, + "cpython-3.10.2+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a696f058a0cf69fa49c7e65c0b0b630c3fdc23474d45795e5c742c474abb8f24", + "variant": "debug" }, "cpython-3.10.0-darwin-aarch64-none": { "name": "cpython", @@ -3668,8 +6664,9 @@ "minor": 10, "patch": 0, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-install_only-20211017T1616.tar.gz", - "sha256": null + "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, + "variant": null }, "cpython-3.10.0-darwin-x86_64-none": { "name": "cpython", @@ -3680,8 +6677,9 @@ "minor": 10, "patch": 0, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-apple-darwin-install_only-20211017T1616.tar.gz", - "sha256": null + "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, + "variant": null }, "cpython-3.10.0-linux-aarch64-gnu": { "name": "cpython", @@ -3693,7 +6691,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-unknown-linux-gnu-lto-20211017T1616.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.10.0-linux-i686-gnu": { "name": "cpython", @@ -3705,7 +6704,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-i686-unknown-linux-gnu-pgo%2Blto-20211017T1616.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.10.0-linux-x86_64-gnu": { "name": "cpython", @@ -3716,8 +6716,9 @@ "minor": 10, "patch": 0, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-unknown-linux-gnu-install_only-20211017T1616.tar.gz", - "sha256": null + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-unknown-linux-gnu-pgo%2Blto-20211017T1616.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.10.0-linux-x86_64-musl": { "name": "cpython", @@ -3729,7 +6730,8 @@ "patch": 0, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.10.0-windows-i686-none": { "name": "cpython", @@ -3741,7 +6743,8 @@ "patch": 0, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.10.0-windows-x86_64-none": { "name": "cpython", @@ -3752,8 +6755,87 @@ "minor": 10, "patch": 0, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-pc-windows-msvc-shared-install_only-20211017T1616.tar.gz", - "sha256": null + "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, + "variant": null + }, + "cpython-3.10.0+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-debug-20211017T1616.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.10.0+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 10, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-apple-darwin-debug-20211017T1616.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.10.0+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 0, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.10.0+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 0, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.10.0+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 10, + "patch": 0, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.10.0+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 10, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-unknown-linux-musl-debug-20211017T1616.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.9.20-darwin-aarch64-none": { "name": "cpython", @@ -3764,8 +6846,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "02c376d7a931c7a4575158e8908978ec7686cd11ba93a0df1770858974f53822" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "41e9bb2d45e1a0467e534dafc6691b3d3c2b79fd9a564562f4c0c41eb343d30a", + "variant": null }, "cpython-3.9.20-darwin-x86_64-none": { "name": "cpython", @@ -3776,8 +6859,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "dddb504549d27beb7ee39e88a9a9db72ac890df987d890edc5fa7310179ebe9f" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "440f4ebc651e707ed24d5dc68d3b0b2197e7fb369bb77685b1b539dbf30ab1e5", + "variant": null }, "cpython-3.9.20-linux-aarch64-gnu": { "name": "cpython", @@ -3788,8 +6872,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "4105f5324b6920af5ebc541f4f29dedd60753ef655c343b77d6835cdb5749d99" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3742c9d6563527a003b12ac689c07e6965911ff89fd9cbbd3c17ac7bfb037d4a", + "variant": null }, "cpython-3.9.20-linux-armv7-gnueabi": { "name": "cpython", @@ -3800,8 +6885,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "7210124060f1958ae4cebee229f311bc7de0081d89ea8d23198ade8e8e7b5b43" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "33f89a84b170bbed966f3028b84f5c39b3c6e30d615585107d69c9ed9fe49564", + "variant": null }, "cpython-3.9.20-linux-armv7-gnueabihf": { "name": "cpython", @@ -3812,8 +6898,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "0ceefbdf74ba55b7d75371b4a0cfec4e0a9dd72693f63236869e288e3bf920b1" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "73ecdd7318b44c88cc5877d039111067723510e922852fd207ac05b03a11863c", + "variant": null }, "cpython-3.9.20-linux-powerpc64le-gnu": { "name": "cpython", @@ -3824,8 +6911,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5700aed0f1c5eec375cc1d356bdd1bcf5844bf58e7b666cc7318c32a61f98953" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "556dd3e80ba644dfb8ca5a8a68681f243717d8ef4a517e486a49e1f6da46278c", + "variant": null }, "cpython-3.9.20-linux-s390x-gnu": { "name": "cpython", @@ -3836,8 +6924,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "88dfa9a5d9ded872716a8bccc7092bbd8a32519c1eaa17799fea45ddc3da19dd" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9eb2296c68484602bf9a27659ca91f6c073c8b1c97c2791bb1b0191aa8b9e45a", + "variant": null }, "cpython-3.9.20-linux-x86_64-gnu": { "name": "cpython", @@ -3848,8 +6937,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "390f555ff4d5f9df83b923f6da4cd71eb88f4400d87f513aaef78b00dd29eade" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "44d9d016f9820f39e5bb542782557d46876b69d23d0a204eb2f367739da623e0", + "variant": null }, "cpython-3.9.20-linux-x86_64-musl": { "name": "cpython", @@ -3860,8 +6950,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0c224f88780706cea5eefe96e3853da8d358ebb1a5f33575097136a7891daa3f" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "332ce515daa15173f73d0ecebc988fadfac5583af8355d8895b3eb8086dac813", + "variant": null }, "cpython-3.9.20-windows-i686-none": { "name": "cpython", @@ -3872,8 +6963,9 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "ef91a17d8f81b3b744be5bfbbd1531963fcfc4798832aae2ac1f8d093354bfee" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4d331f59031e02c857f4afbcfc933de3c68c8fb47ce919103147d760a0d7165f", + "variant": null }, "cpython-3.9.20-windows-x86_64-none": { "name": "cpython", @@ -3884,8 +6976,100 @@ "minor": 9, "patch": 20, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "b7de813b6454e83c0821461b938954b9721f08f0040ed5d08aeeb487fa4c50e4" + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ce3779065ab824333e8d6d0a3d055d4073cdcc9a6e60abe24929023369f91512", + "variant": null + }, + "cpython-3.9.20+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a7822e2a3fa714f4ea2190b6cc19ab5b9197612af7d4f49e158d39864c459ac8", + "variant": "debug" + }, + "cpython-3.9.20+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 9, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "1c436ee44dba41cd14e1b5ddab90a63af9d0e045db838e16d52bdaf9194824aa", + "variant": "debug" + }, + "cpython-3.9.20+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 9, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "fb3c774f9967bdcd35a7ebd28f5c95b737a40e12985baf9ce2ae2207e714e332", + "variant": "debug" + }, + "cpython-3.9.20+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "8063bae3d10f687c0551cc0176730931164aeddd381521d2cea8977ef3154468", + "variant": "debug" + }, + "cpython-3.9.20+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3712d3c6074f536c6ee79db0d29d83ca99065a054d4e22dfb893b63ce3eb28ea", + "variant": "debug" + }, + "cpython-3.9.20+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "25cd1c9e92090f02212a649fa9d5c5dec9842917ea11fa504c66bd02e03d3d15", + "variant": "debug" + }, + "cpython-3.9.20+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "acbe099ea4851fd477b8dbac410ba213331d2129f6e2e4d0967dfb1675799f6c", + "variant": "debug" }, "cpython-3.9.19-darwin-aarch64-none": { "name": "cpython", @@ -3897,7 +7081,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "451582f8a6a8c15ef35a327afcdbf8d03b1ebba7192e90d850d092dac91f91c6" + "sha256": "451582f8a6a8c15ef35a327afcdbf8d03b1ebba7192e90d850d092dac91f91c6", + "variant": null }, "cpython-3.9.19-darwin-x86_64-none": { "name": "cpython", @@ -3909,7 +7094,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "ebaf4336d0cbff4466c994d5bcaa92a38c91d06694d0cd675ec663259d5f37c7" + "sha256": "ebaf4336d0cbff4466c994d5bcaa92a38c91d06694d0cd675ec663259d5f37c7", + "variant": null }, "cpython-3.9.19-linux-aarch64-gnu": { "name": "cpython", @@ -3921,7 +7107,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "23d08f7f0bf151c2ea54b2c9c143bc710faf166ff74225b0f967fab1e2d7a151" + "sha256": "23d08f7f0bf151c2ea54b2c9c143bc710faf166ff74225b0f967fab1e2d7a151", + "variant": null }, "cpython-3.9.19-linux-armv7-gnueabi": { "name": "cpython", @@ -3933,7 +7120,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "cebb879d47874f3f943a4334a8fcd8baa3cd7ef4be8cae6b4c8ae980d981a28d" + "sha256": "cebb879d47874f3f943a4334a8fcd8baa3cd7ef4be8cae6b4c8ae980d981a28d", + "variant": null }, "cpython-3.9.19-linux-armv7-gnueabihf": { "name": "cpython", @@ -3945,7 +7133,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "c32d3227c44919349172c27b35275bad379f1679f729fbd4f336625903171a1a" + "sha256": "c32d3227c44919349172c27b35275bad379f1679f729fbd4f336625903171a1a", + "variant": null }, "cpython-3.9.19-linux-powerpc64le-gnu": { "name": "cpython", @@ -3957,7 +7146,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3cc60442d5694db1abe2a0c6e73459ebb6e7ba7fc7b0a986f3699d463a8f9557" + "sha256": "3cc60442d5694db1abe2a0c6e73459ebb6e7ba7fc7b0a986f3699d463a8f9557", + "variant": null }, "cpython-3.9.19-linux-s390x-gnu": { "name": "cpython", @@ -3969,7 +7159,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b41f834311532ee9dcf76cad3cdeda285d0e283de2182ce9870c37c40970cdd3" + "sha256": "b41f834311532ee9dcf76cad3cdeda285d0e283de2182ce9870c37c40970cdd3", + "variant": null }, "cpython-3.9.19-linux-x86_64-gnu": { "name": "cpython", @@ -3981,7 +7172,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "3b7d574b6bbf8303789a1d26b96a81dcca907381441ce15818c784e18d1db299" + "sha256": "3b7d574b6bbf8303789a1d26b96a81dcca907381441ce15818c784e18d1db299", + "variant": null }, "cpython-3.9.19-linux-x86_64-musl": { "name": "cpython", @@ -3993,7 +7185,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5c6605b1cfa6a952420f2267d10bed9ae20a02858a769b7275d8805f6b9fe40b" + "sha256": "5c6605b1cfa6a952420f2267d10bed9ae20a02858a769b7275d8805f6b9fe40b", + "variant": null }, "cpython-3.9.19-windows-i686-none": { "name": "cpython", @@ -4005,7 +7198,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "1c78c6dd763e6d583c3c3f917544bc4446d0e9fbe2b6e206042fa801ff9fb9ab" + "sha256": "1c78c6dd763e6d583c3c3f917544bc4446d0e9fbe2b6e206042fa801ff9fb9ab", + "variant": null }, "cpython-3.9.19-windows-x86_64-none": { "name": "cpython", @@ -4017,7 +7211,99 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "426da4d31e665b77dacf15cd89494a995ed634a9b97324bbef9cf36fcda4c8a9" + "sha256": "426da4d31e665b77dacf15cd89494a995ed634a9b97324bbef9cf36fcda4c8a9", + "variant": null + }, + "cpython-3.9.19+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "40cc476967b3da12fdf2b0251954c37539d8ae2359f9e54913eff178d666b627", + "variant": "debug" + }, + "cpython-3.9.19+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 9, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "9a861ef69ae7ec4b98bc5612d3410b873bfe7067b93d97566a125b71a33b75da", + "variant": "debug" + }, + "cpython-3.9.19+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": "armv7", + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 9, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "798d8455cb5ce1e9df8e880b17f5852853620184b49c3c9310ae9c7fc2d05470", + "variant": "debug" + }, + "cpython-3.9.19+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "818dd8d3e63722facf8fb92b3c1ffda77dfd0a98e2a8278952ffa41e05b0d5d1", + "variant": "debug" + }, + "cpython-3.9.19+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "dd79bef1974374dcdbe312f1f6cb6cd6ffaa95971b94e01391edb8125b980d90", + "variant": "debug" + }, + "cpython-3.9.19+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9e938140a8ecd343045a0c0cad77165db8bb0b24cc68012f628c937ae8748216", + "variant": "debug" + }, + "cpython-3.9.19+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "bb16ecadf1bbd907da5f0e9c65e2b2bd66770e73c1f3a38ac0ca5fa9ac6fc7a2", + "variant": "debug" }, "cpython-3.9.18-darwin-aarch64-none": { "name": "cpython", @@ -4029,7 +7315,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "2548f911a6e316575c303ba42bb51540dc9b47a9f76a06a2a37460d93b177aa2" + "sha256": "2548f911a6e316575c303ba42bb51540dc9b47a9f76a06a2a37460d93b177aa2", + "variant": null }, "cpython-3.9.18-darwin-x86_64-none": { "name": "cpython", @@ -4041,7 +7328,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "171d8b472fce0295be0e28bb702c43d5a2a39feccb3e72efe620ac3843c3e402" + "sha256": "171d8b472fce0295be0e28bb702c43d5a2a39feccb3e72efe620ac3843c3e402", + "variant": null }, "cpython-3.9.18-linux-aarch64-gnu": { "name": "cpython", @@ -4053,7 +7341,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "e5bc5196baa603d635ee6b0cd141e359752ad3e8ea76127eb9141a3155c51200" + "sha256": "e5bc5196baa603d635ee6b0cd141e359752ad3e8ea76127eb9141a3155c51200", + "variant": null }, "cpython-3.9.18-linux-i686-gnu": { "name": "cpython", @@ -4065,7 +7354,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.9.18%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "10c422080317886057e968010495037ba65731ab7653bcaeabadf67a6fa5e99e" + "sha256": "10c422080317886057e968010495037ba65731ab7653bcaeabadf67a6fa5e99e", + "variant": null }, "cpython-3.9.18-linux-powerpc64le-gnu": { "name": "cpython", @@ -4077,7 +7367,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "d6b18df7a25fe034fd5ce4e64216df2cc78b2d4d908d2a1c94058ae700d73d22" + "sha256": "d6b18df7a25fe034fd5ce4e64216df2cc78b2d4d908d2a1c94058ae700d73d22", + "variant": null }, "cpython-3.9.18-linux-s390x-gnu": { "name": "cpython", @@ -4089,7 +7380,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "15d059507c7e900e9665f31e8d903e5a24a68ceed24f9a1c5ac06ab42a354f3f" + "sha256": "15d059507c7e900e9665f31e8d903e5a24a68ceed24f9a1c5ac06ab42a354f3f", + "variant": null }, "cpython-3.9.18-linux-x86_64-gnu": { "name": "cpython", @@ -4101,7 +7393,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "0e5663025121186bd17d331538a44f48b41baff247891d014f3f962cbe2716b4" + "sha256": "0e5663025121186bd17d331538a44f48b41baff247891d014f3f962cbe2716b4", + "variant": null }, "cpython-3.9.18-linux-x86_64-musl": { "name": "cpython", @@ -4113,7 +7406,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "cb47455810ae63d98501b3bb4fcdfdb9924633fb2e86e62d77e523a3bdee44ba" + "sha256": "cb47455810ae63d98501b3bb4fcdfdb9924633fb2e86e62d77e523a3bdee44ba", + "variant": null }, "cpython-3.9.18-windows-i686-none": { "name": "cpython", @@ -4125,7 +7419,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "904ff5d2f6402640e2b7e2b12075af0bd75b3e8685cc5248fd2a3cda3105d2a8" + "sha256": "904ff5d2f6402640e2b7e2b12075af0bd75b3e8685cc5248fd2a3cda3105d2a8", + "variant": null }, "cpython-3.9.18-windows-x86_64-none": { "name": "cpython", @@ -4137,7 +7432,86 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "a9bdbd728ed4c353a4157ecf74386117fb2a2769a9353f491c528371cfe7f6cd" + "sha256": "a9bdbd728ed4c353a4157ecf74386117fb2a2769a9353f491c528371cfe7f6cd", + "variant": null + }, + "cpython-3.9.18+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 18, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.18+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 18, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.18+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 18, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.18+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 18, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.18+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 18, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.18+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 18, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6f199ddd447d3946381c3ccbb89c0f67643fb8a98205b89caa8e217ad0a20fd7", + "variant": "debug" }, "cpython-3.9.17-darwin-aarch64-none": { "name": "cpython", @@ -4149,7 +7523,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "73dbe2d702210b566221da9265acc274ba15275c5d0d1fa327f44ad86cde9aa1" + "sha256": "73dbe2d702210b566221da9265acc274ba15275c5d0d1fa327f44ad86cde9aa1", + "variant": null }, "cpython-3.9.17-darwin-x86_64-none": { "name": "cpython", @@ -4161,7 +7536,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "dfe1bea92c94b9cb779288b0b06e39157c5ff7e465cdd24032ac147c2af485c0" + "sha256": "dfe1bea92c94b9cb779288b0b06e39157c5ff7e465cdd24032ac147c2af485c0", + "variant": null }, "cpython-3.9.17-linux-aarch64-gnu": { "name": "cpython", @@ -4173,7 +7549,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "b77012ddaf7e0673e4aa4b1c5085275a06eee2d66f33442b5c54a12b62b96cbe" + "sha256": "b77012ddaf7e0673e4aa4b1c5085275a06eee2d66f33442b5c54a12b62b96cbe", + "variant": null }, "cpython-3.9.17-linux-i686-gnu": { "name": "cpython", @@ -4185,7 +7562,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "aed29a64c835444c2f1aff83c55b14123114d74c54d96493a0eabfdd8c6d012c" + "sha256": "aed29a64c835444c2f1aff83c55b14123114d74c54d96493a0eabfdd8c6d012c", + "variant": null }, "cpython-3.9.17-linux-powerpc64le-gnu": { "name": "cpython", @@ -4197,7 +7575,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c591a28d943dce5cf9833e916125fdfbeb3120270c4866ee214493ccb5b83c3c" + "sha256": "c591a28d943dce5cf9833e916125fdfbeb3120270c4866ee214493ccb5b83c3c", + "variant": null }, "cpython-3.9.17-linux-s390x-gnu": { "name": "cpython", @@ -4209,7 +7588,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-s390x-unknown-linux-gnu-install_only.tar.gz", - "sha256": "01454d7cc7c9c2fccde42ba868c4f372eaaafa48049d49dd94c9cf2875f497e6" + "sha256": "01454d7cc7c9c2fccde42ba868c4f372eaaafa48049d49dd94c9cf2875f497e6", + "variant": null }, "cpython-3.9.17-linux-x86_64-gnu": { "name": "cpython", @@ -4221,7 +7601,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "26c4a712b4b8e11ed5c027db5654eb12927c02da4857b777afb98f7a930ce637" + "sha256": "26c4a712b4b8e11ed5c027db5654eb12927c02da4857b777afb98f7a930ce637", + "variant": null }, "cpython-3.9.17-linux-x86_64-musl": { "name": "cpython", @@ -4233,7 +7614,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "194316e9cc7add1dd12be3e3eea2908fd4d623799edd7df69e360c6a446b750d" + "sha256": "194316e9cc7add1dd12be3e3eea2908fd4d623799edd7df69e360c6a446b750d", + "variant": null }, "cpython-3.9.17-windows-i686-none": { "name": "cpython", @@ -4245,7 +7627,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "09f9d4bc66be5e0df2dfd1dc4742923e46c271f8f085178696c77073477aa0c1" + "sha256": "09f9d4bc66be5e0df2dfd1dc4742923e46c271f8f085178696c77073477aa0c1", + "variant": null }, "cpython-3.9.17-windows-x86_64-none": { "name": "cpython", @@ -4257,7 +7640,86 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "9b9a1e21eff29dcf043cea38180cf8ca3604b90117d00062a7b31605d4157714" + "sha256": "9b9a1e21eff29dcf043cea38180cf8ca3604b90117d00062a7b31605d4157714", + "variant": null + }, + "cpython-3.9.17+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 17, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.17+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 17, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.17+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 17, + "prerelease": "", + "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": "bcb0ec31342df52b4555be309080a9c3224e7ff60a6291e34337ddfddef111cf", + "variant": "debug" + }, + "cpython-3.9.17+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": "s390x", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 17, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.17+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 17, + "prerelease": "", + "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": "649fff6048f4cb9e64a85eaf8e720eb4c3257e27e7c4ee46f75bfa48c18c6826", + "variant": "debug" + }, + "cpython-3.9.17+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 17, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5a117bc64d6545821083afbfb4a381afee05ddaeb0dd45d2c5adbf3b0a67ec88", + "variant": "debug" }, "cpython-3.9.16-darwin-aarch64-none": { "name": "cpython", @@ -4269,7 +7731,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "c1de1d854717a6245f45262ef1bb17b09e2c587590e7e3f406593c143ff875bd" + "sha256": "c1de1d854717a6245f45262ef1bb17b09e2c587590e7e3f406593c143ff875bd", + "variant": null }, "cpython-3.9.16-darwin-x86_64-none": { "name": "cpython", @@ -4281,7 +7744,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "3abc4d5fbbc80f5f848f280927ac5d13de8dc03aabb6ae65d8247cbb68e6f6bf" + "sha256": "3abc4d5fbbc80f5f848f280927ac5d13de8dc03aabb6ae65d8247cbb68e6f6bf", + "variant": null }, "cpython-3.9.16-linux-aarch64-gnu": { "name": "cpython", @@ -4293,7 +7757,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "f629b75ebfcafe9ceee2e796b7e4df5cf8dbd14f3c021afca078d159ab797acf" + "sha256": "f629b75ebfcafe9ceee2e796b7e4df5cf8dbd14f3c021afca078d159ab797acf", + "variant": null }, "cpython-3.9.16-linux-i686-gnu": { "name": "cpython", @@ -4305,7 +7770,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "ab0a14b3ae72bf48b94820e096e86b3cf3e05729862f768e109aa8318016c4f2" + "sha256": "ab0a14b3ae72bf48b94820e096e86b3cf3e05729862f768e109aa8318016c4f2", + "variant": null }, "cpython-3.9.16-linux-powerpc64le-gnu": { "name": "cpython", @@ -4317,7 +7783,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz", - "sha256": "ff3ac35c58f67839aff9b5185a976abd3d1abbe61af02089f7105e876c1fe284" + "sha256": "ff3ac35c58f67839aff9b5185a976abd3d1abbe61af02089f7105e876c1fe284", + "variant": null }, "cpython-3.9.16-linux-x86_64-gnu": { "name": "cpython", @@ -4329,7 +7796,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "2b6e146234a4ef2a8946081fc3fbfffe0765b80b690425a49ebe40b47c33445b" + "sha256": "2b6e146234a4ef2a8946081fc3fbfffe0765b80b690425a49ebe40b47c33445b", + "variant": null }, "cpython-3.9.16-linux-x86_64-musl": { "name": "cpython", @@ -4341,7 +7809,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "5d9b13e8d5ee7a26fd0cf6e6d7e5a1ea90ddddd1f30ed2400bda60506f7dcea3" + "sha256": "5d9b13e8d5ee7a26fd0cf6e6d7e5a1ea90ddddd1f30ed2400bda60506f7dcea3", + "variant": null }, "cpython-3.9.16-windows-i686-none": { "name": "cpython", @@ -4353,7 +7822,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "219532ffa49af88e3b90e9135cf3b6e1fa11cf165b03098fb9776a07af8ca6d0" + "sha256": "219532ffa49af88e3b90e9135cf3b6e1fa11cf165b03098fb9776a07af8ca6d0", + "variant": null }, "cpython-3.9.16-windows-x86_64-none": { "name": "cpython", @@ -4365,7 +7835,73 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "cdabb47204e96ce7ea31fbd0b5ed586114dd7d8f8eddf60a509a7f70b48a1c5e" + "sha256": "cdabb47204e96ce7ea31fbd0b5ed586114dd7d8f8eddf60a509a7f70b48a1c5e", + "variant": null + }, + "cpython-3.9.16+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 16, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.16+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 16, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.16+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": "powerpc64le", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 16, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.16+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 16, + "prerelease": "", + "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": "cdc1290b9bdb2f74a6c48ab24531919551128e39773365c6f3e17668216275a0", + "variant": "debug" + }, + "cpython-3.9.16+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 16, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "e698c7a51e7e998e893b0dd25886d477778a14296bd560e6e585352b9d6194f8", + "variant": "debug" }, "cpython-3.9.15-darwin-aarch64-none": { "name": "cpython", @@ -4377,7 +7913,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "64dc7e1013481c9864152c3dd806c41144c79d5e9cd3140e185c6a5060bdc9ab" + "sha256": "64dc7e1013481c9864152c3dd806c41144c79d5e9cd3140e185c6a5060bdc9ab", + "variant": null }, "cpython-3.9.15-darwin-x86_64-none": { "name": "cpython", @@ -4389,7 +7926,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "f2bcade6fc976c472f18f2b3204d67202d43ae55cf6f9e670f95e488f780da08" + "sha256": "f2bcade6fc976c472f18f2b3204d67202d43ae55cf6f9e670f95e488f780da08", + "variant": null }, "cpython-3.9.15-linux-aarch64-gnu": { "name": "cpython", @@ -4401,7 +7939,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "52a8c0a67fb919f80962d992da1bddb511cdf92faf382701ce7673e10a8ff98f" + "sha256": "52a8c0a67fb919f80962d992da1bddb511cdf92faf382701ce7673e10a8ff98f", + "variant": null }, "cpython-3.9.15-linux-i686-gnu": { "name": "cpython", @@ -4413,7 +7952,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "bf32a86c220e4d1690bb92b67653f20b8325808accd81bff03b5c30ae74e6444" + "sha256": "bf32a86c220e4d1690bb92b67653f20b8325808accd81bff03b5c30ae74e6444", + "variant": null }, "cpython-3.9.15-linux-x86_64-gnu": { "name": "cpython", @@ -4425,7 +7965,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "cdc3a4cfddcd63b6cebdd75b14970e02d8ef0ac5be4d350e57ab5df56c19e85e" + "sha256": "cdc3a4cfddcd63b6cebdd75b14970e02d8ef0ac5be4d350e57ab5df56c19e85e", + "variant": null }, "cpython-3.9.15-linux-x86_64-musl": { "name": "cpython", @@ -4437,7 +7978,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "81b1c76ac789521fcececdcdc643f6de6fc282083b1a36a9973d835fc8a39391" + "sha256": "81b1c76ac789521fcececdcdc643f6de6fc282083b1a36a9973d835fc8a39391", + "variant": null }, "cpython-3.9.15-windows-i686-none": { "name": "cpython", @@ -4449,7 +7991,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "0b81089247f258f244e9792daaa03675da6f58597daa6913e82f2679862238dd" + "sha256": "0b81089247f258f244e9792daaa03675da6f58597daa6913e82f2679862238dd", + "variant": null }, "cpython-3.9.15-windows-x86_64-none": { "name": "cpython", @@ -4461,7 +8004,60 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "022daacab215679b87f0d200d08b9068a721605fa4721ebeda38220fc641ccf6" + "sha256": "022daacab215679b87f0d200d08b9068a721605fa4721ebeda38220fc641ccf6", + "variant": null + }, + "cpython-3.9.15+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 15, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.15+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 15, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.15+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 15, + "prerelease": "", + "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": "6a08761bb725b8d3a92144f81628febeab8b12326ca264ffe28255fa67c7bf17", + "variant": "debug" + }, + "cpython-3.9.15+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c4bf3bd40dc301eb41984b205a204fb85ebe190bfe35fe73c79b749b48ec7a31", + "variant": "debug" }, "cpython-3.9.14-darwin-aarch64-none": { "name": "cpython", @@ -4473,7 +8069,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "e38df7f230979ce6c53a5bafb3a81287838e5f3892c40cd1b98a0c961c444713" + "sha256": "e38df7f230979ce6c53a5bafb3a81287838e5f3892c40cd1b98a0c961c444713", + "variant": null }, "cpython-3.9.14-darwin-x86_64-none": { "name": "cpython", @@ -4485,7 +8082,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "b7d3a1f4b57e9350571ccee49c82f503133de0d113a2dbaebc8ccf108fb3fe1b" + "sha256": "b7d3a1f4b57e9350571ccee49c82f503133de0d113a2dbaebc8ccf108fb3fe1b", + "variant": null }, "cpython-3.9.14-linux-aarch64-gnu": { "name": "cpython", @@ -4497,7 +8095,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "fe538201559ca37f44cd5f66c42a65fe7272cb4f1f63edd698b6f306771db1e9" + "sha256": "fe538201559ca37f44cd5f66c42a65fe7272cb4f1f63edd698b6f306771db1e9", + "variant": null }, "cpython-3.9.14-linux-i686-gnu": { "name": "cpython", @@ -4509,7 +8108,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "3af1c255110c2f42ed0b7957502c92edf8b5c5e6fc5f699a2475bf8a560325c0" + "sha256": "3af1c255110c2f42ed0b7957502c92edf8b5c5e6fc5f699a2475bf8a560325c0", + "variant": null }, "cpython-3.9.14-linux-x86_64-gnu": { "name": "cpython", @@ -4521,7 +8121,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "e63d0c00a499e0202ba7a0f53ce69fca6d30237af39af9bc3c76bce6c7bf14d7" + "sha256": "e63d0c00a499e0202ba7a0f53ce69fca6d30237af39af9bc3c76bce6c7bf14d7", + "variant": null }, "cpython-3.9.14-linux-x86_64-musl": { "name": "cpython", @@ -4533,7 +8134,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "ceb26ef5f5a9b7b47fa95225fffce9c8ef0c9c1fbeca69fbda236a0c10de7ad8" + "sha256": "ceb26ef5f5a9b7b47fa95225fffce9c8ef0c9c1fbeca69fbda236a0c10de7ad8", + "variant": null }, "cpython-3.9.14-windows-i686-none": { "name": "cpython", @@ -4545,7 +8147,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "f3526e8416be86ff9091750ebc7388d6726acf32cc5ab0e6a60c67c6aacb2569" + "sha256": "f3526e8416be86ff9091750ebc7388d6726acf32cc5ab0e6a60c67c6aacb2569", + "variant": null }, "cpython-3.9.14-windows-x86_64-none": { "name": "cpython", @@ -4557,7 +8160,60 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "f111c3c129f4a5a171d25350ce58dad4c7e58fbe664e9b4f7c275345c9fe18a6" + "sha256": "f111c3c129f4a5a171d25350ce58dad4c7e58fbe664e9b4f7c275345c9fe18a6", + "variant": null + }, + "cpython-3.9.14+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 14, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.14+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 14, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.14+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 14, + "prerelease": "", + "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": "54529c0a8ffe621f5c9c6bdd22968cac9d3207cbd5dcd9c07bbe61140c49937e", + "variant": "debug" + }, + "cpython-3.9.14+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "598de16fbb955c2de02ac7765f162bee74601e28b3de9b6efeeecf30d97aecd6", + "variant": "debug" }, "cpython-3.9.13-darwin-aarch64-none": { "name": "cpython", @@ -4569,7 +8225,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "d9603edc296a2dcbc59d7ada780fd12527f05c3e0b99f7545112daf11636d6e5" + "sha256": "d9603edc296a2dcbc59d7ada780fd12527f05c3e0b99f7545112daf11636d6e5", + "variant": null }, "cpython-3.9.13-darwin-x86_64-none": { "name": "cpython", @@ -4581,7 +8238,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "9540a7efb7c8a54a48aff1cb9480e49588d9c0a3f934ad53f5b167338174afa3" + "sha256": "9540a7efb7c8a54a48aff1cb9480e49588d9c0a3f934ad53f5b167338174afa3", + "variant": null }, "cpython-3.9.13-linux-aarch64-gnu": { "name": "cpython", @@ -4593,7 +8251,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "80415aac1b96255b9211f6a4c300f31e9940c7e07a23d0dec12b53aa52c0d25e" + "sha256": "80415aac1b96255b9211f6a4c300f31e9940c7e07a23d0dec12b53aa52c0d25e", + "variant": null }, "cpython-3.9.13-linux-i686-gnu": { "name": "cpython", @@ -4605,7 +8264,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "efcc8fef0d498afe576ab209fee001fda3b552de1a85f621f2602787aa6cf3d4" + "sha256": "efcc8fef0d498afe576ab209fee001fda3b552de1a85f621f2602787aa6cf3d4", + "variant": null }, "cpython-3.9.13-linux-x86_64-gnu": { "name": "cpython", @@ -4617,7 +8277,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "ce1cfca2715e7e646dd618a8cb9baff93000e345ccc979b801fc6ccde7ce97df" + "sha256": "ce1cfca2715e7e646dd618a8cb9baff93000e345ccc979b801fc6ccde7ce97df", + "variant": null }, "cpython-3.9.13-linux-x86_64-musl": { "name": "cpython", @@ -4629,7 +8290,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "766ed7e805e8c7abc4e50f1c94814575e7978ed7bd1f9e9ccec82d66b47b567f" + "sha256": "766ed7e805e8c7abc4e50f1c94814575e7978ed7bd1f9e9ccec82d66b47b567f", + "variant": null }, "cpython-3.9.13-windows-i686-none": { "name": "cpython", @@ -4641,7 +8303,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "90e3879382f06fea3ba6d477f0c2a434a1e14cd83d174e1c7b87e2f22bc2e748" + "sha256": "90e3879382f06fea3ba6d477f0c2a434a1e14cd83d174e1c7b87e2f22bc2e748", + "variant": null }, "cpython-3.9.13-windows-x86_64-none": { "name": "cpython", @@ -4653,7 +8316,60 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "b538127025a467c64b3351babca2e4d2ea7bdfb7867d5febb3529c34456cdcd4" + "sha256": "b538127025a467c64b3351babca2e4d2ea7bdfb7867d5febb3529c34456cdcd4", + "variant": null + }, + "cpython-3.9.13+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.13+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.13+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 13, + "prerelease": "", + "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": "352d00a4630d0665387bcb158aec3f6c7fc5a4d14d65ac26e1b826e20611222f", + "variant": "debug" + }, + "cpython-3.9.13+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 13, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "fd18bcb560764e7456431b577ce3b35057c3dd4cda60dfb98a9af8739ec95c43", + "variant": "debug" }, "cpython-3.9.12-darwin-aarch64-none": { "name": "cpython", @@ -4665,7 +8381,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "8dee06c07cc6429df34b6abe091a4684a86f7cec76f5d1ccc1c3ce2bd11168df" + "sha256": "8dee06c07cc6429df34b6abe091a4684a86f7cec76f5d1ccc1c3ce2bd11168df", + "variant": null }, "cpython-3.9.12-darwin-x86_64-none": { "name": "cpython", @@ -4677,7 +8394,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "2453ba7f76b3df3310353b48c881d6cff622ba06e30d2b6ae91588b2bc9e481a" + "sha256": "2453ba7f76b3df3310353b48c881d6cff622ba06e30d2b6ae91588b2bc9e481a", + "variant": null }, "cpython-3.9.12-linux-aarch64-gnu": { "name": "cpython", @@ -4689,7 +8407,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "2ee1426c181e65133e57dc55c6a685cb1fb5e63ef02d684b8a667d5c031c4203" + "sha256": "2ee1426c181e65133e57dc55c6a685cb1fb5e63ef02d684b8a667d5c031c4203", + "variant": null }, "cpython-3.9.12-linux-i686-gnu": { "name": "cpython", @@ -4701,7 +8420,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "233e1a9626d9fe13baac8de3689df48401d0ad5da1c2f134ad57d8e3e878a1a5" + "sha256": "233e1a9626d9fe13baac8de3689df48401d0ad5da1c2f134ad57d8e3e878a1a5", + "variant": null }, "cpython-3.9.12-linux-x86_64-gnu": { "name": "cpython", @@ -4713,7 +8433,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "ccca12f698b3b810d79c52f007078f520d588232a36bc12ede944ec3ea417816" + "sha256": "ccca12f698b3b810d79c52f007078f520d588232a36bc12ede944ec3ea417816", + "variant": null }, "cpython-3.9.12-linux-x86_64-musl": { "name": "cpython", @@ -4725,7 +8446,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "dd0eaf7ef64008d4a51a73243f368e0311b7936b0ac18f8d1305fffb0dfb76e6" + "sha256": "dd0eaf7ef64008d4a51a73243f368e0311b7936b0ac18f8d1305fffb0dfb76e6", + "variant": null }, "cpython-3.9.12-windows-i686-none": { "name": "cpython", @@ -4737,7 +8459,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "8b7e440137bfa349a008641a75a2b1fd8ae22d290731778a144878a59a721c51" + "sha256": "8b7e440137bfa349a008641a75a2b1fd8ae22d290731778a144878a59a721c51", + "variant": null }, "cpython-3.9.12-windows-x86_64-none": { "name": "cpython", @@ -4749,7 +8472,60 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "3024147fd987d9e1b064a3d94932178ff8e0fe98cfea955704213c0762fee8df" + "sha256": "3024147fd987d9e1b064a3d94932178ff8e0fe98cfea955704213c0762fee8df", + "variant": null + }, + "cpython-3.9.12+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 12, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.12+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 12, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.12+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 12, + "prerelease": "", + "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": "7290ac14e43749afdb37d3c9690f300f5f0786f19982e8960566ecdc3e42c3eb", + "variant": "debug" + }, + "cpython-3.9.12+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 12, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "410d7e68366f0e4fce4540a73ab3228060aa469949154fc08dc00d9da8ad51c6", + "variant": "debug" }, "cpython-3.9.11-darwin-aarch64-none": { "name": "cpython", @@ -4761,7 +8537,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "cf92a28f98c8d884df0937bf19d5f1a40caa25a6a211a237b7e9b592b2b71c2b" + "sha256": "cf92a28f98c8d884df0937bf19d5f1a40caa25a6a211a237b7e9b592b2b71c2b", + "variant": null }, "cpython-3.9.11-darwin-x86_64-none": { "name": "cpython", @@ -4773,7 +8550,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "43889d1a424c84fb155e1619f062adb6984fbde80b6043611790f22bcbeec300" + "sha256": "43889d1a424c84fb155e1619f062adb6984fbde80b6043611790f22bcbeec300", + "variant": null }, "cpython-3.9.11-linux-aarch64-gnu": { "name": "cpython", @@ -4785,7 +8563,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "0e50f099409c5e651b5fddd16124af1d830d11653e786a93c28e5b8f8aa470c4" + "sha256": "0e50f099409c5e651b5fddd16124af1d830d11653e786a93c28e5b8f8aa470c4", + "variant": null }, "cpython-3.9.11-linux-i686-gnu": { "name": "cpython", @@ -4797,7 +8576,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "75ac727631eab002bd120246197a8235145cb90687be181f7a52de6f41d44d34" + "sha256": "75ac727631eab002bd120246197a8235145cb90687be181f7a52de6f41d44d34", + "variant": null }, "cpython-3.9.11-linux-x86_64-gnu": { "name": "cpython", @@ -4809,7 +8589,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "0429d5ceb095d5e24c292bf1a39208b88ae236a680ef8fa3e1830e3a1a7e8882" + "sha256": "0429d5ceb095d5e24c292bf1a39208b88ae236a680ef8fa3e1830e3a1a7e8882", + "variant": null }, "cpython-3.9.11-linux-x86_64-musl": { "name": "cpython", @@ -4821,7 +8602,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "ae8f55d90ae173f96e81f376daa5a9969a77531a6f7b8eacbe8ad90b41bbca1d" + "sha256": "ae8f55d90ae173f96e81f376daa5a9969a77531a6f7b8eacbe8ad90b41bbca1d", + "variant": null }, "cpython-3.9.11-windows-i686-none": { "name": "cpython", @@ -4833,7 +8615,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "ceac8729b285a8c8e861176dd2dadd7f8e7e26d8f64cac6c6226a14d2252cd4c" + "sha256": "ceac8729b285a8c8e861176dd2dadd7f8e7e26d8f64cac6c6226a14d2252cd4c", + "variant": null }, "cpython-3.9.11-windows-x86_64-none": { "name": "cpython", @@ -4845,7 +8628,60 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "0c529a511f7a03908fc126c4a8467b47e24a4d98812147e8e786cf59e86febf0" + "sha256": "0c529a511f7a03908fc126c4a8467b47e24a4d98812147e8e786cf59e86febf0", + "variant": null + }, + "cpython-3.9.11+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 11, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.11+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 11, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.11+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 11, + "prerelease": "", + "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": "020bcbfff16dc5ce35a898763be3d847c97df2e14dabf483a8ec88b0455ff971", + "variant": "debug" + }, + "cpython-3.9.11+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 11, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "046c027a79ab60b70f2853ef978d89a97d2d35d9af3e7d7e9684d20b66afa6bc", + "variant": "debug" }, "cpython-3.9.10-darwin-aarch64-none": { "name": "cpython", @@ -4857,7 +8693,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "ad66c2a3e7263147e046a32694de7b897a46fb0124409d29d3a93ede631c8aee" + "sha256": "ad66c2a3e7263147e046a32694de7b897a46fb0124409d29d3a93ede631c8aee", + "variant": null }, "cpython-3.9.10-darwin-x86_64-none": { "name": "cpython", @@ -4869,7 +8706,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "fdaf594142446029e314a9beb91f1ac75af866320b50b8b968181e592550cd68" + "sha256": "fdaf594142446029e314a9beb91f1ac75af866320b50b8b968181e592550cd68", + "variant": null }, "cpython-3.9.10-linux-aarch64-gnu": { "name": "cpython", @@ -4881,7 +8719,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "12dd1f125762f47975990ec744532a1cf3db74ad60f4dfb476ca42deb7f78ca4" + "sha256": "12dd1f125762f47975990ec744532a1cf3db74ad60f4dfb476ca42deb7f78ca4", + "variant": null }, "cpython-3.9.10-linux-i686-gnu": { "name": "cpython", @@ -4893,7 +8732,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "37ba43845c3df9ba012d69121ad29ea7f21ea2f5994a155007cf1560d74ce503" + "sha256": "37ba43845c3df9ba012d69121ad29ea7f21ea2f5994a155007cf1560d74ce503", + "variant": null }, "cpython-3.9.10-linux-x86_64-gnu": { "name": "cpython", @@ -4905,7 +8745,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "455089cc576bd9a58db45e919d1fc867ecdbb0208067dffc845cc9bbf0701b70" + "sha256": "455089cc576bd9a58db45e919d1fc867ecdbb0208067dffc845cc9bbf0701b70", + "variant": null }, "cpython-3.9.10-linux-x86_64-musl": { "name": "cpython", @@ -4917,7 +8758,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "30add63ec16e07ad13e19f6d7061f7e4c7b971962354f48ab3e85656ce3b393d" + "sha256": "30add63ec16e07ad13e19f6d7061f7e4c7b971962354f48ab3e85656ce3b393d", + "variant": null }, "cpython-3.9.10-windows-i686-none": { "name": "cpython", @@ -4929,7 +8771,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "56c0342a9af0412676e89cdf7b52ac76037031786b3f5c40942b8b82d366c96f" + "sha256": "56c0342a9af0412676e89cdf7b52ac76037031786b3f5c40942b8b82d366c96f", + "variant": null }, "cpython-3.9.10-windows-x86_64-none": { "name": "cpython", @@ -4941,7 +8784,86 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "c145d9d8143ce163670af124b623d7a2405143a3708b033b4d33eed355e61b24" + "sha256": "c145d9d8143ce163670af124b623d7a2405143a3708b033b4d33eed355e61b24", + "variant": null + }, + "cpython-3.9.10+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220222/cpython-3.9.10-aarch64-apple-darwin-debug-20220220T1113.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.10+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220222/cpython-3.9.10-x86_64-apple-darwin-debug-20220220T1113.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.10+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 10, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.10+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 10, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.9.10+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 10, + "prerelease": "", + "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": "d453abf741c3196ffc8470f3ea6404a3e2b55b2674a501bb79162f06122423e5", + "variant": "debug" + }, + "cpython-3.9.10+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "3ea1f4b06710a87a4f817b9084f60cc7ea481eb1090adc81306031ac4b382131", + "variant": "debug" }, "cpython-3.9.7-darwin-aarch64-none": { "name": "cpython", @@ -4952,8 +8874,9 @@ "minor": 9, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-apple-darwin-install_only-20211017T1616.tar.gz", - "sha256": null + "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, + "variant": null }, "cpython-3.9.7-darwin-x86_64-none": { "name": "cpython", @@ -4964,8 +8887,9 @@ "minor": 9, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-apple-darwin-install_only-20211017T1616.tar.gz", - "sha256": null + "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, + "variant": null }, "cpython-3.9.7-linux-aarch64-gnu": { "name": "cpython", @@ -4977,7 +8901,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-unknown-linux-gnu-lto-20211017T1616.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.7-linux-i686-gnu": { "name": "cpython", @@ -4989,7 +8914,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-i686-unknown-linux-gnu-pgo%2Blto-20211017T1616.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.7-linux-x86_64-gnu": { "name": "cpython", @@ -5000,8 +8926,9 @@ "minor": 9, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-unknown-linux-gnu-install_only-20211017T1616.tar.gz", - "sha256": null + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-unknown-linux-gnu-pgo%2Blto-20211017T1616.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.9.7-linux-x86_64-musl": { "name": "cpython", @@ -5013,7 +8940,8 @@ "patch": 7, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.7-windows-i686-none": { "name": "cpython", @@ -5025,7 +8953,8 @@ "patch": 7, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.7-windows-x86_64-none": { "name": "cpython", @@ -5036,8 +8965,87 @@ "minor": 9, "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-pc-windows-msvc-shared-install_only-20211017T1616.tar.gz", - "sha256": null + "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, + "variant": null + }, + "cpython-3.9.7+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-apple-darwin-debug-20211017T1616.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.7+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-apple-darwin-debug-20211017T1616.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.7+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 7, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.9.7+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 7, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.9.7+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 7, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.9.7+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-unknown-linux-musl-debug-20211017T1616.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.9.6-darwin-aarch64-none": { "name": "cpython", @@ -5048,8 +9056,9 @@ "minor": 9, "patch": 6, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-apple-darwin-install_only-20210724T1424.tar.gz", - "sha256": null + "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, + "variant": null }, "cpython-3.9.6-darwin-x86_64-none": { "name": "cpython", @@ -5060,8 +9069,9 @@ "minor": 9, "patch": 6, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-apple-darwin-install_only-20210724T1424.tar.gz", - "sha256": null + "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, + "variant": null }, "cpython-3.9.6-linux-aarch64-gnu": { "name": "cpython", @@ -5073,7 +9083,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-unknown-linux-gnu-lto-20210724T1424.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.6-linux-i686-gnu": { "name": "cpython", @@ -5085,7 +9096,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-i686-unknown-linux-gnu-pgo%2Blto-20210724T1424.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.6-linux-x86_64-gnu": { "name": "cpython", @@ -5096,8 +9108,9 @@ "minor": 9, "patch": 6, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-unknown-linux-gnu-install_only-20210724T1424.tar.gz", - "sha256": null + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-unknown-linux-gnu-pgo%2Blto-20210724T1424.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.9.6-linux-x86_64-musl": { "name": "cpython", @@ -5109,7 +9122,8 @@ "patch": 6, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.6-windows-i686-none": { "name": "cpython", @@ -5121,7 +9135,8 @@ "patch": 6, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.6-windows-x86_64-none": { "name": "cpython", @@ -5132,8 +9147,87 @@ "minor": 9, "patch": 6, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-pc-windows-msvc-shared-install_only-20210724T1424.tar.gz", - "sha256": null + "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, + "variant": null + }, + "cpython-3.9.6+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-apple-darwin-debug-20210724T1424.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.6+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-apple-darwin-debug-20210724T1424.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.6+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 6, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.9.6+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 6, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.9.6+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 6, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.9.6+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-unknown-linux-musl-debug-20210724T1424.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.9.5-darwin-aarch64-none": { "name": "cpython", @@ -5145,7 +9239,8 @@ "patch": 5, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.5-darwin-x86_64-none": { "name": "cpython", @@ -5157,7 +9252,8 @@ "patch": 5, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.5-linux-i686-gnu": { "name": "cpython", @@ -5169,7 +9265,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-i686-unknown-linux-gnu-pgo%2Blto-20210506T0943.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.5-linux-x86_64-gnu": { "name": "cpython", @@ -5181,7 +9278,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-unknown-linux-gnu-pgo%2Blto-20210506T0943.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.5-linux-x86_64-musl": { "name": "cpython", @@ -5193,7 +9291,8 @@ "patch": 5, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.5-windows-i686-none": { "name": "cpython", @@ -5205,7 +9304,8 @@ "patch": 5, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.5-windows-x86_64-none": { "name": "cpython", @@ -5217,7 +9317,73 @@ "patch": 5, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.9.5+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-aarch64-apple-darwin-debug-20210506T0943.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.5+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-apple-darwin-debug-20210506T0943.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.5+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 5, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.9.5+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 5, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.9.5+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-unknown-linux-musl-debug-20210506T0943.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.9.4-darwin-aarch64-none": { "name": "cpython", @@ -5229,7 +9395,8 @@ "patch": 4, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.4-darwin-x86_64-none": { "name": "cpython", @@ -5241,7 +9408,8 @@ "patch": 4, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.4-linux-i686-gnu": { "name": "cpython", @@ -5253,7 +9421,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-i686-unknown-linux-gnu-pgo%2Blto-20210414T1515.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.4-linux-x86_64-gnu": { "name": "cpython", @@ -5265,7 +9434,8 @@ "patch": 4, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-unknown-linux-gnu-pgo%2Blto-20210414T1515.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.4-linux-x86_64-musl": { "name": "cpython", @@ -5277,7 +9447,8 @@ "patch": 4, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.4-windows-i686-none": { "name": "cpython", @@ -5289,7 +9460,8 @@ "patch": 4, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.4-windows-x86_64-none": { "name": "cpython", @@ -5301,7 +9473,73 @@ "patch": 4, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.9.4+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-aarch64-apple-darwin-debug-20210414T1515.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.4+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-apple-darwin-debug-20210414T1515.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.4+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 4, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.9.4+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 4, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.9.4+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 4, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-unknown-linux-musl-debug-20210414T1515.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.9.3-darwin-aarch64-none": { "name": "cpython", @@ -5313,7 +9551,8 @@ "patch": 3, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.3-darwin-x86_64-none": { "name": "cpython", @@ -5325,7 +9564,8 @@ "patch": 3, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.3-linux-x86_64-gnu": { "name": "cpython", @@ -5337,7 +9577,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-unknown-linux-gnu-pgo%2Blto-20210413T2055.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.3-linux-x86_64-musl": { "name": "cpython", @@ -5349,7 +9590,8 @@ "patch": 3, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.3-windows-i686-none": { "name": "cpython", @@ -5361,7 +9603,8 @@ "patch": 3, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.3-windows-x86_64-none": { "name": "cpython", @@ -5373,7 +9616,60 @@ "patch": 3, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.9.3+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-aarch64-apple-darwin-debug-20210413T2055.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.3+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-apple-darwin-debug-20210413T2055.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.3+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 3, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.9.3+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-unknown-linux-musl-debug-20210413T2055.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.9.2-darwin-aarch64-none": { "name": "cpython", @@ -5385,7 +9681,8 @@ "patch": 2, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.2-darwin-x86_64-none": { "name": "cpython", @@ -5397,7 +9694,8 @@ "patch": 2, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.2-linux-i686-gnu": { "name": "cpython", @@ -5409,7 +9707,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-i686-unknown-linux-gnu-pgo%2Blto-20210327T1202.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.2-linux-x86_64-gnu": { "name": "cpython", @@ -5421,7 +9720,8 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-unknown-linux-gnu-pgo%2Blto-20210327T1202.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.2-linux-x86_64-musl": { "name": "cpython", @@ -5433,7 +9733,8 @@ "patch": 2, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.2-windows-i686-none": { "name": "cpython", @@ -5445,7 +9746,8 @@ "patch": 2, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.2-windows-x86_64-none": { "name": "cpython", @@ -5457,7 +9759,73 @@ "patch": 2, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.9.2+debug-darwin-aarch64-none": { + "name": "cpython", + "arch": "aarch64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-aarch64-apple-darwin-debug-20210327T1202.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.2+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-apple-darwin-debug-20210327T1202.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.2+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 2, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.9.2+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 2, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.9.2+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-unknown-linux-musl-debug-20210327T1202.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.9.1-darwin-x86_64-none": { "name": "cpython", @@ -5469,7 +9837,8 @@ "patch": 1, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.1-linux-x86_64-gnu": { "name": "cpython", @@ -5481,7 +9850,8 @@ "patch": 1, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-unknown-linux-gnu-pgo-20210103T1125.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.1-linux-x86_64-musl": { "name": "cpython", @@ -5492,8 +9862,9 @@ "minor": 9, "patch": 1, "prerelease": "", - "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 + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-unknown-linux-musl-noopt-20210103T1125.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.9.1-windows-i686-none": { "name": "cpython", @@ -5505,7 +9876,8 @@ "patch": 1, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.1-windows-x86_64-none": { "name": "cpython", @@ -5517,7 +9889,47 @@ "patch": 1, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.9.1+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 1, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-apple-darwin-debug-20210103T1125.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.1+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 1, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.9.1+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 1, + "prerelease": "", + "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, + "variant": "debug" }, "cpython-3.9.0-darwin-x86_64-none": { "name": "cpython", @@ -5529,7 +9941,8 @@ "patch": 0, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.0-linux-x86_64-gnu": { "name": "cpython", @@ -5541,7 +9954,8 @@ "patch": 0, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-unknown-linux-gnu-pgo-20201020T0627.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.9.0-linux-x86_64-musl": { "name": "cpython", @@ -5552,8 +9966,9 @@ "minor": 9, "patch": 0, "prerelease": "", - "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 + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-unknown-linux-musl-noopt-20201020T0627.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.9.0-windows-i686-none": { "name": "cpython", @@ -5565,7 +9980,8 @@ "patch": 0, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.9.0-windows-x86_64-none": { "name": "cpython", @@ -5577,7 +9993,47 @@ "patch": 0, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.9.0+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 9, + "patch": 0, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-apple-darwin-debug-20201020T0626.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.9.0+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 9, + "patch": 0, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.9.0+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 9, + "patch": 0, + "prerelease": "", + "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, + "variant": "debug" }, "cpython-3.8.20-darwin-aarch64-none": { "name": "cpython", @@ -5589,7 +10045,8 @@ "patch": 20, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "30ba44af64e599bde7307908393374bdcd99e185bf9b3c9de3f697f3fbe6bf8f" + "sha256": "30ba44af64e599bde7307908393374bdcd99e185bf9b3c9de3f697f3fbe6bf8f", + "variant": null }, "cpython-3.8.20-darwin-x86_64-none": { "name": "cpython", @@ -5601,7 +10058,8 @@ "patch": 20, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "375b6eead6c852cabbf3ccfd43dc4f6dd4c36381bf74c9a7910acb839fd5c57f" + "sha256": "375b6eead6c852cabbf3ccfd43dc4f6dd4c36381bf74c9a7910acb839fd5c57f", + "variant": null }, "cpython-3.8.20-linux-aarch64-gnu": { "name": "cpython", @@ -5613,7 +10071,8 @@ "patch": 20, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "75a187ebfab81096e3f3d91d70c1349e64defbdfb0e8a067cb5233d017655e31" + "sha256": "75a187ebfab81096e3f3d91d70c1349e64defbdfb0e8a067cb5233d017655e31", + "variant": null }, "cpython-3.8.20-linux-x86_64-gnu": { "name": "cpython", @@ -5625,7 +10084,8 @@ "patch": 20, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a3a75094545912d4e9413673441b3f0d2e58ce9b264477f910800148801ccf11" + "sha256": "a3a75094545912d4e9413673441b3f0d2e58ce9b264477f910800148801ccf11", + "variant": null }, "cpython-3.8.20-linux-x86_64-musl": { "name": "cpython", @@ -5637,7 +10097,8 @@ "patch": 20, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "fcddfd3f1090833e1f3106be021809630008b53026bc96dcaab2986625db27fa" + "sha256": "fcddfd3f1090833e1f3106be021809630008b53026bc96dcaab2986625db27fa", + "variant": null }, "cpython-3.8.20-windows-i686-none": { "name": "cpython", @@ -5649,7 +10110,8 @@ "patch": 20, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "d829105aaf53a1cadf8738e040c6211bc9bef2c6e4757b972954f0f322d57e7d" + "sha256": "d829105aaf53a1cadf8738e040c6211bc9bef2c6e4757b972954f0f322d57e7d", + "variant": null }, "cpython-3.8.20-windows-x86_64-none": { "name": "cpython", @@ -5661,7 +10123,47 @@ "patch": 20, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "ec2f723dcfbf09581578a716c05cc67823a43d77111e6dd9e0d1557ccc6dcbf3" + "sha256": "ec2f723dcfbf09581578a716c05cc67823a43d77111e6dd9e0d1557ccc6dcbf3", + "variant": null + }, + "cpython-3.8.20+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e9e2244eeb0d849925c63e077b8e659e50af7b8faa7c778bea49012a356cd9b5", + "variant": "debug" + }, + "cpython-3.8.20+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "311c259280fb53844d281f2c2a2f4d7343fc520628a79cff488ee7e045843751", + "variant": "debug" + }, + "cpython-3.8.20+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 20, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "451432b8c869484464f26690839857dd7380aa588c3c052314c5324a26b5727e", + "variant": "debug" }, "cpython-3.8.19-darwin-aarch64-none": { "name": "cpython", @@ -5673,7 +10175,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "6a15ee2b507aed4d5b15fd1b66fc570aa49183f15aa6c412eccd065446f17d8e" + "sha256": "6a15ee2b507aed4d5b15fd1b66fc570aa49183f15aa6c412eccd065446f17d8e", + "variant": null }, "cpython-3.8.19-darwin-x86_64-none": { "name": "cpython", @@ -5685,7 +10188,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1a24263b039c1172bd42d74a5694492f3e3dbe4d3e52a1e7cc2856fee7dbee4a" + "sha256": "1a24263b039c1172bd42d74a5694492f3e3dbe4d3e52a1e7cc2856fee7dbee4a", + "variant": null }, "cpython-3.8.19-linux-aarch64-gnu": { "name": "cpython", @@ -5697,7 +10201,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "202211923850303f521146ee1831642aaf357ffeeadbe13a0a91884317227528" + "sha256": "202211923850303f521146ee1831642aaf357ffeeadbe13a0a91884317227528", + "variant": null }, "cpython-3.8.19-linux-x86_64-gnu": { "name": "cpython", @@ -5709,7 +10214,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0f1579dbb01c98af7a12fef4c9aa8a99d45b91393f64431f5de712f892bc5c0b" + "sha256": "0f1579dbb01c98af7a12fef4c9aa8a99d45b91393f64431f5de712f892bc5c0b", + "variant": null }, "cpython-3.8.19-linux-x86_64-musl": { "name": "cpython", @@ -5721,7 +10227,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "6ee6c7469c9d2c7078beb95a9a3a261c42502e0b1603722a0689bdb2e789060c" + "sha256": "6ee6c7469c9d2c7078beb95a9a3a261c42502e0b1603722a0689bdb2e789060c", + "variant": null }, "cpython-3.8.19-windows-i686-none": { "name": "cpython", @@ -5733,7 +10240,8 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "73bf0135330b96c48ca79ccd6d2f3287a7466573a5fc1b62d982bcdb1d5f0ab3" + "sha256": "73bf0135330b96c48ca79ccd6d2f3287a7466573a5fc1b62d982bcdb1d5f0ab3", + "variant": null }, "cpython-3.8.19-windows-x86_64-none": { "name": "cpython", @@ -5745,7 +10253,47 @@ "patch": 19, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "89d238b125cd7546b7d0cbd7f484a438d2c2f239c15c9b38ec3c62b1f343a6ca" + "sha256": "89d238b125cd7546b7d0cbd7f484a438d2c2f239c15c9b38ec3c62b1f343a6ca", + "variant": null + }, + "cpython-3.8.19+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d4c0800b9684236145310c6eda4505ba28cee3a3f10e8b22929a406b32b5f4fc", + "variant": "debug" + }, + "cpython-3.8.19+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0bd4dc46b451d40c04ff329ecb30e6a9eb375973d9457421e7afaaaa86614f74", + "variant": "debug" + }, + "cpython-3.8.19+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 19, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a165c74be970b1804bda7acba186b7e3b96bd76e4e38fafd95bf7a40e21c8128", + "variant": "debug" }, "cpython-3.8.18-darwin-aarch64-none": { "name": "cpython", @@ -5757,7 +10305,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "4d493a1792bf211f37f98404cc1468f09bd781adc2602dea0df82ad264c11abc" + "sha256": "4d493a1792bf211f37f98404cc1468f09bd781adc2602dea0df82ad264c11abc", + "variant": null }, "cpython-3.8.18-darwin-x86_64-none": { "name": "cpython", @@ -5769,7 +10318,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "7d2cd8d289d5e3cdd0a8c06c028c7c621d3d00ce44b7e2f08c1724ae0471c626" + "sha256": "7d2cd8d289d5e3cdd0a8c06c028c7c621d3d00ce44b7e2f08c1724ae0471c626", + "variant": null }, "cpython-3.8.18-linux-aarch64-gnu": { "name": "cpython", @@ -5781,7 +10331,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "6588c9eed93833d9483d01fe40ac8935f691a1af8e583d404ec7666631b52487" + "sha256": "6588c9eed93833d9483d01fe40ac8935f691a1af8e583d404ec7666631b52487", + "variant": null }, "cpython-3.8.18-linux-x86_64-gnu": { "name": "cpython", @@ -5793,7 +10344,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "5ae36825492372554c02708bdd26b8dcd57e3dbf34b3d6d599ad91d93540b2b7" + "sha256": "5ae36825492372554c02708bdd26b8dcd57e3dbf34b3d6d599ad91d93540b2b7", + "variant": null }, "cpython-3.8.18-linux-x86_64-musl": { "name": "cpython", @@ -5805,7 +10357,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "e591d3925f88f78a5dffb765fd10b9dab6e497d35cf58169da83eab521c86a37" + "sha256": "e591d3925f88f78a5dffb765fd10b9dab6e497d35cf58169da83eab521c86a37", + "variant": null }, "cpython-3.8.18-windows-i686-none": { "name": "cpython", @@ -5817,7 +10370,8 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "c24f9c9e8638cff0ce6aa808a57cc5f22009bc33e3bcf410a726b79d7c5545fe" + "sha256": "c24f9c9e8638cff0ce6aa808a57cc5f22009bc33e3bcf410a726b79d7c5545fe", + "variant": null }, "cpython-3.8.18-windows-x86_64-none": { "name": "cpython", @@ -5829,7 +10383,47 @@ "patch": 18, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "dba923ee5df8f99db04f599e826be92880746c02247c8d8e4d955d4bc711af11" + "sha256": "dba923ee5df8f99db04f599e826be92880746c02247c8d8e4d955d4bc711af11", + "variant": null + }, + "cpython-3.8.18+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 18, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.18+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 18, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.18+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 18, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "375d241bbd7d8704794fbda9387de8328999b5fb377f091ebb026bdace4abc24", + "variant": "debug" }, "cpython-3.8.17-darwin-aarch64-none": { "name": "cpython", @@ -5841,7 +10435,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "c6f7a130d0044a78e39648f4dae56dcff5a41eba91888a99f6e560507162e6a1" + "sha256": "c6f7a130d0044a78e39648f4dae56dcff5a41eba91888a99f6e560507162e6a1", + "variant": null }, "cpython-3.8.17-darwin-x86_64-none": { "name": "cpython", @@ -5853,7 +10448,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "155b06821607bae1a58ecc60a7d036b358c766f19e493b8876190765c883a5c2" + "sha256": "155b06821607bae1a58ecc60a7d036b358c766f19e493b8876190765c883a5c2", + "variant": null }, "cpython-3.8.17-linux-aarch64-gnu": { "name": "cpython", @@ -5865,7 +10461,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "9f6d585091fe26906ff1dbb80437a3fe37a1e3db34d6ecc0098f3d6a78356682" + "sha256": "9f6d585091fe26906ff1dbb80437a3fe37a1e3db34d6ecc0098f3d6a78356682", + "variant": null }, "cpython-3.8.17-linux-i686-gnu": { "name": "cpython", @@ -5877,7 +10474,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "e580fdd923bbae612334559dc58bd5fd13cce53b769294d63bc88e7c6662f7d9" + "sha256": "e580fdd923bbae612334559dc58bd5fd13cce53b769294d63bc88e7c6662f7d9", + "variant": null }, "cpython-3.8.17-linux-x86_64-gnu": { "name": "cpython", @@ -5889,7 +10487,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "8d3e1826c0bb7821ec63288038644808a2d45553245af106c685ef5892fabcd8" + "sha256": "8d3e1826c0bb7821ec63288038644808a2d45553245af106c685ef5892fabcd8", + "variant": null }, "cpython-3.8.17-linux-x86_64-musl": { "name": "cpython", @@ -5901,7 +10500,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "322b7837cfd8282c62ae3d2f0e98f0843cbe287e4b8c4852b786123f2e13b307" + "sha256": "322b7837cfd8282c62ae3d2f0e98f0843cbe287e4b8c4852b786123f2e13b307", + "variant": null }, "cpython-3.8.17-windows-i686-none": { "name": "cpython", @@ -5913,7 +10513,8 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "cb6af626ba811044e9c5ee09140a6920565d2b1b237a11886b96354a9fcc242e" + "sha256": "cb6af626ba811044e9c5ee09140a6920565d2b1b237a11886b96354a9fcc242e", + "variant": null }, "cpython-3.8.17-windows-x86_64-none": { "name": "cpython", @@ -5925,7 +10526,60 @@ "patch": 17, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "6428e1b4e0b4482d390828de7d4c82815257443416cb786abe10cb2466ca68cd" + "sha256": "6428e1b4e0b4482d390828de7d4c82815257443416cb786abe10cb2466ca68cd", + "variant": null + }, + "cpython-3.8.17+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 17, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.17+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 17, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.17+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 17, + "prerelease": "", + "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": "f499750ab0019f36ccb4d964e222051d0d49a1d1e8dbada98abae738cf48c9dc", + "variant": "debug" + }, + "cpython-3.8.17+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 17, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c396b744f28286fc0980073efd08ce55eef5bd0197a4944c3bae5c339ef91269", + "variant": "debug" }, "cpython-3.8.16-darwin-aarch64-none": { "name": "cpython", @@ -5937,7 +10591,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "7e484eb6de40d6f6bdfd5099eaa9647f65e45fb6d846ccfc56b1cb1e38b5ab02" + "sha256": "7e484eb6de40d6f6bdfd5099eaa9647f65e45fb6d846ccfc56b1cb1e38b5ab02", + "variant": null }, "cpython-3.8.16-darwin-x86_64-none": { "name": "cpython", @@ -5949,7 +10604,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "28506e509646c11cb2f57a7203bd1b08b6e8e5b159ae308bd5bb93b0d334bdaf" + "sha256": "28506e509646c11cb2f57a7203bd1b08b6e8e5b159ae308bd5bb93b0d334bdaf", + "variant": null }, "cpython-3.8.16-linux-aarch64-gnu": { "name": "cpython", @@ -5961,7 +10617,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "9c6615931fd1045bf9f2148aa7dd9ce1ece8575ed68a5483a0b615322a43d54c" + "sha256": "9c6615931fd1045bf9f2148aa7dd9ce1ece8575ed68a5483a0b615322a43d54c", + "variant": null }, "cpython-3.8.16-linux-i686-gnu": { "name": "cpython", @@ -5973,7 +10630,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "1260fd6af34104bbd57489175e6f7bfea76d4bd06a242a0f8e20e390e870b227" + "sha256": "1260fd6af34104bbd57489175e6f7bfea76d4bd06a242a0f8e20e390e870b227", + "variant": null }, "cpython-3.8.16-linux-x86_64-gnu": { "name": "cpython", @@ -5985,7 +10643,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "b1f1502c3a13b899724dbd32bd77a973fa9733b932c5700d747fe33d5de9ac4f" + "sha256": "b1f1502c3a13b899724dbd32bd77a973fa9733b932c5700d747fe33d5de9ac4f", + "variant": null }, "cpython-3.8.16-linux-x86_64-musl": { "name": "cpython", @@ -5997,7 +10656,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "840aefa3b03b66b6561360735dc0ac4e0a36a3ebb4d1f85d92f5b5f6638953cc" + "sha256": "840aefa3b03b66b6561360735dc0ac4e0a36a3ebb4d1f85d92f5b5f6638953cc", + "variant": null }, "cpython-3.8.16-windows-i686-none": { "name": "cpython", @@ -6009,7 +10669,8 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "77466f93ef5b030cf13d0446067089b0ce0d415cc6d1702655bdbb12a8c18c97" + "sha256": "77466f93ef5b030cf13d0446067089b0ce0d415cc6d1702655bdbb12a8c18c97", + "variant": null }, "cpython-3.8.16-windows-x86_64-none": { "name": "cpython", @@ -6021,7 +10682,60 @@ "patch": 16, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "120b3312fa79bac2ace45641171c2bc590c4e4462d7ad124d64597e124a36ae7" + "sha256": "120b3312fa79bac2ace45641171c2bc590c4e4462d7ad124d64597e124a36ae7", + "variant": null + }, + "cpython-3.8.16+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 16, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.16+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 16, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.16+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 16, + "prerelease": "", + "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": "f12f5cb38f796ca48dc73262c05506a6f21f59d24e709ea0390b18bf71c2e1f9", + "variant": "debug" + }, + "cpython-3.8.16+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 16, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "54898fbd313530295cf103bb4e7c7922096ed1c56f46a955975ecd4fb5b02987", + "variant": "debug" }, "cpython-3.8.15-darwin-aarch64-none": { "name": "cpython", @@ -6033,7 +10747,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "1e0a92d1a4f5e6d4a99f86b1cbf9773d703fe7fd032590f3e9c285c7a5eeb00a" + "sha256": "1e0a92d1a4f5e6d4a99f86b1cbf9773d703fe7fd032590f3e9c285c7a5eeb00a", + "variant": null }, "cpython-3.8.15-darwin-x86_64-none": { "name": "cpython", @@ -6045,7 +10760,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "70b57f28c2b5e1e3dd89f0d30edd5bc414e8b20195766cf328e1b26bed7890e1" + "sha256": "70b57f28c2b5e1e3dd89f0d30edd5bc414e8b20195766cf328e1b26bed7890e1", + "variant": null }, "cpython-3.8.15-linux-aarch64-gnu": { "name": "cpython", @@ -6057,7 +10773,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "886ab33ced13c84bf59ce8ff79eba6448365bfcafea1bf415bd1d75e21b690aa" + "sha256": "886ab33ced13c84bf59ce8ff79eba6448365bfcafea1bf415bd1d75e21b690aa", + "variant": null }, "cpython-3.8.15-linux-i686-gnu": { "name": "cpython", @@ -6069,7 +10786,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "3bc1f49147913d93cea9cbb753fbaae90b86f1ee979f975c4712a35f02cbd86b" + "sha256": "3bc1f49147913d93cea9cbb753fbaae90b86f1ee979f975c4712a35f02cbd86b", + "variant": null }, "cpython-3.8.15-linux-x86_64-gnu": { "name": "cpython", @@ -6081,7 +10799,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "e47edfb2ceaf43fc699e20c179ec428b6f3e497cf8e2dcd8e9c936d4b96b1e56" + "sha256": "e47edfb2ceaf43fc699e20c179ec428b6f3e497cf8e2dcd8e9c936d4b96b1e56", + "variant": null }, "cpython-3.8.15-linux-x86_64-musl": { "name": "cpython", @@ -6093,7 +10812,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "f767d0438eca5b18c1267c5121055a5808a1412ea7668ef17da3dc9bdd24a55f" + "sha256": "f767d0438eca5b18c1267c5121055a5808a1412ea7668ef17da3dc9bdd24a55f", + "variant": null }, "cpython-3.8.15-windows-i686-none": { "name": "cpython", @@ -6105,7 +10825,8 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "318c059324b84b5d7685bcd0874698799d9e3689b51dbcf596e7a47a39a3d49a" + "sha256": "318c059324b84b5d7685bcd0874698799d9e3689b51dbcf596e7a47a39a3d49a", + "variant": null }, "cpython-3.8.15-windows-x86_64-none": { "name": "cpython", @@ -6117,7 +10838,60 @@ "patch": 15, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "2fdc3fa1c95f982179bbbaedae2b328197658638799b6dcb63f9f494b0de59e2" + "sha256": "2fdc3fa1c95f982179bbbaedae2b328197658638799b6dcb63f9f494b0de59e2", + "variant": null + }, + "cpython-3.8.15+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 15, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.15+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 15, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.15+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 15, + "prerelease": "", + "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": "c3c8c23e34bddb4a2b90333ff17041f344401775d505700f1ceddb3ad9d589e0", + "variant": "debug" + }, + "cpython-3.8.15+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 15, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ef23e1c394127b770995e021aff6b1ee3580d5c4f3d12abd6d5c64e007b972cf", + "variant": "debug" }, "cpython-3.8.14-darwin-aarch64-none": { "name": "cpython", @@ -6129,7 +10903,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "6c17f6dcda59de5d8eee922ef7eede403a540dae05423ef2c2a042d8d4f22467" + "sha256": "6c17f6dcda59de5d8eee922ef7eede403a540dae05423ef2c2a042d8d4f22467", + "variant": null }, "cpython-3.8.14-darwin-x86_64-none": { "name": "cpython", @@ -6141,7 +10916,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "3ed4db8d0308c584196d97c629058ea69bbd8b7f9a034cf8c2c701ebb286c091" + "sha256": "3ed4db8d0308c584196d97c629058ea69bbd8b7f9a034cf8c2c701ebb286c091", + "variant": null }, "cpython-3.8.14-linux-aarch64-gnu": { "name": "cpython", @@ -6153,7 +10929,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "c45e42deee43e3ebc4ca5b019c37d8ae25fb5b5f1ba5f602098a81b99d2bc804" + "sha256": "c45e42deee43e3ebc4ca5b019c37d8ae25fb5b5f1ba5f602098a81b99d2bc804", + "variant": null }, "cpython-3.8.14-linux-i686-gnu": { "name": "cpython", @@ -6165,7 +10942,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "d01d813939ad549ca253c52e5b8361b4490cc5c8cbda00ab6e0c524565153e2b" + "sha256": "d01d813939ad549ca253c52e5b8361b4490cc5c8cbda00ab6e0c524565153e2b", + "variant": null }, "cpython-3.8.14-linux-x86_64-gnu": { "name": "cpython", @@ -6177,7 +10955,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "4eb53bce831bf52682067579c09ccaccb6524dd44bd4b8047454c69b4817f4f0" + "sha256": "4eb53bce831bf52682067579c09ccaccb6524dd44bd4b8047454c69b4817f4f0", + "variant": null }, "cpython-3.8.14-linux-x86_64-musl": { "name": "cpython", @@ -6189,7 +10968,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "72c08b1c1d8cc14cb8d22eab18b463bb514ea160472fdc7400bd69ae375cf9c4" + "sha256": "72c08b1c1d8cc14cb8d22eab18b463bb514ea160472fdc7400bd69ae375cf9c4", + "variant": null }, "cpython-3.8.14-windows-i686-none": { "name": "cpython", @@ -6201,7 +10981,8 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "a0730f3a9e60581f02bdb852953fbb52cf98e8431259fa39cb668a060bd002a0" + "sha256": "a0730f3a9e60581f02bdb852953fbb52cf98e8431259fa39cb668a060bd002a0", + "variant": null }, "cpython-3.8.14-windows-x86_64-none": { "name": "cpython", @@ -6213,7 +10994,60 @@ "patch": 14, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "1af39953b4c8324ed0608e316bc763006f27e76643155d92eae18e4db6fc162f" + "sha256": "1af39953b4c8324ed0608e316bc763006f27e76643155d92eae18e4db6fc162f", + "variant": null + }, + "cpython-3.8.14+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 14, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.14+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 14, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.14+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 14, + "prerelease": "", + "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": "bd1e8e09edccaab82fbd75b457205a076847d62e3354c3d9b5abe985181047fc", + "variant": "debug" + }, + "cpython-3.8.14+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 14, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "a63a6565153b0f8b2e23611c7622ded3b5cb488d090f26fb4895bd396f8f72ea", + "variant": "debug" }, "cpython-3.8.13-darwin-aarch64-none": { "name": "cpython", @@ -6225,7 +11059,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "ae4131253d890b013171cb5f7b03cadc585ae263719506f7b7e063a7cf6fde76" + "sha256": "ae4131253d890b013171cb5f7b03cadc585ae263719506f7b7e063a7cf6fde76", + "variant": null }, "cpython-3.8.13-darwin-x86_64-none": { "name": "cpython", @@ -6237,7 +11072,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "cd6e7c0a27daf7df00f6882eaba01490dd963f698e99aeee9706877333e0df69" + "sha256": "cd6e7c0a27daf7df00f6882eaba01490dd963f698e99aeee9706877333e0df69", + "variant": null }, "cpython-3.8.13-linux-aarch64-gnu": { "name": "cpython", @@ -6249,7 +11085,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "8dc7814bf3425bbf78c6e6e5a6529ded6ae463fa6a4b79c025b343bae4fd955a" + "sha256": "8dc7814bf3425bbf78c6e6e5a6529ded6ae463fa6a4b79c025b343bae4fd955a", + "variant": null }, "cpython-3.8.13-linux-i686-gnu": { "name": "cpython", @@ -6261,7 +11098,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "9485599ad9053dfba08c91854717272e95b7c81e0d099d9c51a46fc5a095ccb4" + "sha256": "9485599ad9053dfba08c91854717272e95b7c81e0d099d9c51a46fc5a095ccb4", + "variant": null }, "cpython-3.8.13-linux-x86_64-gnu": { "name": "cpython", @@ -6273,7 +11111,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "fb566629ccb5f76ef56d275a3f8017d683f1c20c5beb5d5f38b155ed11e16187" + "sha256": "fb566629ccb5f76ef56d275a3f8017d683f1c20c5beb5d5f38b155ed11e16187", + "variant": null }, "cpython-3.8.13-linux-x86_64-musl": { "name": "cpython", @@ -6285,7 +11124,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "2c90a0d048caf146d4c33560d6eead1428a225219018d364b1af77f23c492984" + "sha256": "2c90a0d048caf146d4c33560d6eead1428a225219018d364b1af77f23c492984", + "variant": null }, "cpython-3.8.13-windows-i686-none": { "name": "cpython", @@ -6297,7 +11137,8 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "a50668d4c5fbcb374d3ca93ee18db910bc3b462693db073669f31e6da993abf9" + "sha256": "a50668d4c5fbcb374d3ca93ee18db910bc3b462693db073669f31e6da993abf9", + "variant": null }, "cpython-3.8.13-windows-x86_64-none": { "name": "cpython", @@ -6309,7 +11150,60 @@ "patch": 13, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "f20643f1b3e263a56287319aea5c3888530c09ad9de3a5629b1a5d207807e6b9" + "sha256": "f20643f1b3e263a56287319aea5c3888530c09ad9de3a5629b1a5d207807e6b9", + "variant": null + }, + "cpython-3.8.13+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": "aarch64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.13+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 13, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.13+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 13, + "prerelease": "", + "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": "891b5d7b0e936b98a62f65bc0b28fff61ca9002125a2fc1ebb9c72f6b0056712", + "variant": "debug" + }, + "cpython-3.8.13+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 13, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "45802706624fe2b2aa57bd42418b5d2f9f94f5372f31c664762393316223389e", + "variant": "debug" }, "cpython-3.8.12-darwin-aarch64-none": { "name": "cpython", @@ -6321,7 +11215,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-aarch64-apple-darwin-install_only.tar.gz", - "sha256": "f9a3cbb81e0463d6615125964762d133387d561b226a30199f5b039b20f1d944" + "sha256": "f9a3cbb81e0463d6615125964762d133387d561b226a30199f5b039b20f1d944", + "variant": null }, "cpython-3.8.12-darwin-x86_64-none": { "name": "cpython", @@ -6333,7 +11228,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-apple-darwin-install_only.tar.gz", - "sha256": "f323fbc558035c13a85ce2267d0fad9e89282268ecb810e364fff1d0a079d525" + "sha256": "f323fbc558035c13a85ce2267d0fad9e89282268ecb810e364fff1d0a079d525", + "variant": null }, "cpython-3.8.12-linux-i686-gnu": { "name": "cpython", @@ -6345,7 +11241,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-i686-unknown-linux-gnu-install_only.tar.gz", - "sha256": "fcb2033f01a2b10a51be68c9a1b4c7d7759b582f58a503371fe67ab59987b418" + "sha256": "fcb2033f01a2b10a51be68c9a1b4c7d7759b582f58a503371fe67ab59987b418", + "variant": null }, "cpython-3.8.12-linux-x86_64-gnu": { "name": "cpython", @@ -6357,7 +11254,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", - "sha256": "5be9c6d61e238b90dfd94755051c0d3a2d8023ebffdb4b0fa4e8fedd09a6cab6" + "sha256": "5be9c6d61e238b90dfd94755051c0d3a2d8023ebffdb4b0fa4e8fedd09a6cab6", + "variant": null }, "cpython-3.8.12-linux-x86_64-musl": { "name": "cpython", @@ -6369,7 +11267,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-unknown-linux-musl-install_only.tar.gz", - "sha256": "27faf8aa62de2cd4e59b75a6edce4cab549eba81f0f9cc21df0e370a8a2f3a25" + "sha256": "27faf8aa62de2cd4e59b75a6edce4cab549eba81f0f9cc21df0e370a8a2f3a25", + "variant": null }, "cpython-3.8.12-windows-i686-none": { "name": "cpython", @@ -6381,7 +11280,8 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-i686-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "aaa75b9115af73dc3daf7db050ed4f60fd67d2a23ebab30670f18fb8cfa71f33" + "sha256": "aaa75b9115af73dc3daf7db050ed4f60fd67d2a23ebab30670f18fb8cfa71f33", + "variant": null }, "cpython-3.8.12-windows-x86_64-none": { "name": "cpython", @@ -6393,7 +11293,60 @@ "patch": 12, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", - "sha256": "4658e08a00d60b1e01559b74d58ff4dd04da6df935d55f6268a15d6d0a679d74" + "sha256": "4658e08a00d60b1e01559b74d58ff4dd04da6df935d55f6268a15d6d0a679d74", + "variant": null + }, + "cpython-3.8.12+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 12, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220222/cpython-3.8.12-x86_64-apple-darwin-debug-20220220T1113.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.12+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 12, + "prerelease": "", + "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", + "variant": "debug" + }, + "cpython-3.8.12+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 12, + "prerelease": "", + "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": "9ad20c520c291d08087e9afb4390f389d2b66c7fc97f23fffc1313ebafc5fee0", + "variant": "debug" + }, + "cpython-3.8.12+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 12, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "10bca8ab83f2ebb0f7b308a9e2bfebe9a0e638485f6947da9cdb26d95889ef32", + "variant": "debug" }, "cpython-3.8.11-darwin-x86_64-none": { "name": "cpython", @@ -6405,7 +11358,8 @@ "patch": 11, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.11-linux-i686-gnu": { "name": "cpython", @@ -6417,7 +11371,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-i686-unknown-linux-gnu-pgo%2Blto-20210724T1424.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.11-linux-x86_64-gnu": { "name": "cpython", @@ -6429,7 +11384,8 @@ "patch": 11, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-unknown-linux-gnu-pgo%2Blto-20210724T1424.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.11-linux-x86_64-musl": { "name": "cpython", @@ -6441,7 +11397,8 @@ "patch": 11, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.11-windows-i686-none": { "name": "cpython", @@ -6453,7 +11410,8 @@ "patch": 11, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.11-windows-x86_64-none": { "name": "cpython", @@ -6465,7 +11423,60 @@ "patch": 11, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.11+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 11, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-apple-darwin-debug-20210724T1424.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.11+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 11, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.8.11+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 11, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.8.11+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 11, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-unknown-linux-musl-debug-20210724T1424.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.8.10-darwin-x86_64-none": { "name": "cpython", @@ -6477,7 +11488,8 @@ "patch": 10, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.10-linux-i686-gnu": { "name": "cpython", @@ -6489,7 +11501,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-i686-unknown-linux-gnu-pgo%2Blto-20210506T0943.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.10-linux-x86_64-gnu": { "name": "cpython", @@ -6501,7 +11514,8 @@ "patch": 10, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-unknown-linux-gnu-pgo%2Blto-20210506T0943.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.10-linux-x86_64-musl": { "name": "cpython", @@ -6513,7 +11527,8 @@ "patch": 10, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.10-windows-i686-none": { "name": "cpython", @@ -6525,7 +11540,8 @@ "patch": 10, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.10-windows-x86_64-none": { "name": "cpython", @@ -6537,7 +11553,60 @@ "patch": 10, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.10+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-apple-darwin-debug-20210506T0943.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.10+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 10, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.8.10+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 10, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.8.10+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 10, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-unknown-linux-musl-debug-20210506T0943.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.8.9-darwin-x86_64-none": { "name": "cpython", @@ -6549,7 +11618,8 @@ "patch": 9, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.9-linux-i686-gnu": { "name": "cpython", @@ -6561,7 +11631,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-i686-unknown-linux-gnu-pgo%2Blto-20210414T1515.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.9-linux-x86_64-gnu": { "name": "cpython", @@ -6573,7 +11644,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-unknown-linux-gnu-pgo%2Blto-20210414T1515.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.9-linux-x86_64-musl": { "name": "cpython", @@ -6585,7 +11657,8 @@ "patch": 9, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.9-windows-i686-none": { "name": "cpython", @@ -6597,7 +11670,8 @@ "patch": 9, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.9-windows-x86_64-none": { "name": "cpython", @@ -6609,7 +11683,60 @@ "patch": 9, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.9+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-apple-darwin-debug-20210414T1515.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.9+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 9, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.8.9+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 9, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.8.9+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-unknown-linux-musl-debug-20210414T1515.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.8.8-darwin-x86_64-none": { "name": "cpython", @@ -6621,7 +11748,8 @@ "patch": 8, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.8-linux-i686-gnu": { "name": "cpython", @@ -6633,7 +11761,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-i686-unknown-linux-gnu-pgo%2Blto-20210327T1202.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.8-linux-x86_64-gnu": { "name": "cpython", @@ -6645,7 +11774,8 @@ "patch": 8, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-unknown-linux-gnu-pgo%2Blto-20210327T1202.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.8-linux-x86_64-musl": { "name": "cpython", @@ -6657,7 +11787,8 @@ "patch": 8, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.8-windows-i686-none": { "name": "cpython", @@ -6669,7 +11800,8 @@ "patch": 8, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.8-windows-x86_64-none": { "name": "cpython", @@ -6681,7 +11813,60 @@ "patch": 8, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.8+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 8, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210325/cpython-3.8.8-x86_64-apple-darwin-debug-20210325T0901.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.8+debug-linux-i686-gnu": { + "name": "cpython", + "arch": "i686", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 8, + "prerelease": "", + "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, + "variant": "debug" + }, + "cpython-3.8.8+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 8, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.8.8+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 8, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-unknown-linux-musl-debug-20210327T1202.tar.zst", + "sha256": null, + "variant": "debug" }, "cpython-3.8.7-darwin-x86_64-none": { "name": "cpython", @@ -6693,7 +11878,8 @@ "patch": 7, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.7-linux-x86_64-gnu": { "name": "cpython", @@ -6705,7 +11891,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-unknown-linux-gnu-pgo-20210103T1125.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.7-linux-x86_64-musl": { "name": "cpython", @@ -6716,8 +11903,9 @@ "minor": 8, "patch": 7, "prerelease": "", - "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 + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-unknown-linux-musl-noopt-20210103T1125.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.8.7-windows-i686-none": { "name": "cpython", @@ -6729,7 +11917,8 @@ "patch": 7, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.7-windows-x86_64-none": { "name": "cpython", @@ -6741,7 +11930,47 @@ "patch": 7, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.7+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 7, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-apple-darwin-debug-20210103T1125.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.7+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 7, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.8.7+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 7, + "prerelease": "", + "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, + "variant": "debug" }, "cpython-3.8.6-darwin-x86_64-none": { "name": "cpython", @@ -6753,7 +11982,8 @@ "patch": 6, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.6-linux-x86_64-gnu": { "name": "cpython", @@ -6765,7 +11995,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-unknown-linux-gnu-pgo-20201020T0627.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.6-linux-x86_64-musl": { "name": "cpython", @@ -6776,8 +12007,9 @@ "minor": 8, "patch": 6, "prerelease": "", - "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 + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-unknown-linux-musl-noopt-20201020T0627.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.8.6-windows-i686-none": { "name": "cpython", @@ -6789,7 +12021,8 @@ "patch": 6, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.6-windows-x86_64-none": { "name": "cpython", @@ -6801,7 +12034,47 @@ "patch": 6, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.6+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 6, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-apple-darwin-debug-20201020T0626.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.6+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 6, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.8.6+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 6, + "prerelease": "", + "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, + "variant": "debug" }, "cpython-3.8.5-darwin-x86_64-none": { "name": "cpython", @@ -6813,7 +12086,8 @@ "patch": 5, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.5-linux-x86_64-gnu": { "name": "cpython", @@ -6825,7 +12099,8 @@ "patch": 5, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.8.5-x86_64-unknown-linux-gnu-pgo-20200823T0036.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.5-linux-x86_64-musl": { "name": "cpython", @@ -6836,8 +12111,9 @@ "minor": 8, "patch": 5, "prerelease": "", - "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 + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.8.5-x86_64-unknown-linux-musl-noopt-20200823T0036.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.8.5-windows-i686-none": { "name": "cpython", @@ -6849,7 +12125,8 @@ "patch": 5, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.5-windows-x86_64-none": { "name": "cpython", @@ -6861,7 +12138,47 @@ "patch": 5, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.5+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 5, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.8.5-x86_64-apple-darwin-debug-20200823T2228.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.5+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 5, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.8.5+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 5, + "prerelease": "", + "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, + "variant": "debug" }, "cpython-3.8.3-darwin-x86_64-none": { "name": "cpython", @@ -6873,7 +12190,8 @@ "patch": 3, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.3-linux-x86_64-gnu": { "name": "cpython", @@ -6885,7 +12203,8 @@ "patch": 3, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-unknown-linux-gnu-pgo-20200518T0040.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.8.3-linux-x86_64-musl": { "name": "cpython", @@ -6896,8 +12215,9 @@ "minor": 8, "patch": 3, "prerelease": "", - "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 + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-unknown-linux-musl-noopt-20200518T0040.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.8.3-windows-i686-none": { "name": "cpython", @@ -6909,7 +12229,8 @@ "patch": 3, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.3-windows-x86_64-none": { "name": "cpython", @@ -6921,7 +12242,47 @@ "patch": 3, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.3+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 3, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200530/cpython-3.8.3-x86_64-apple-darwin-debug-20200530T1845.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.3+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 3, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.8.3+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 3, + "prerelease": "", + "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, + "variant": "debug" }, "cpython-3.8.2-darwin-x86_64-none": { "name": "cpython", @@ -6933,7 +12294,8 @@ "patch": 2, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.2-linux-x86_64-gnu": { "name": "cpython", @@ -6945,7 +12307,21 @@ "patch": 2, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-unknown-linux-gnu-pgo-20200418T2243.tar.zst", - "sha256": null + "sha256": null, + "variant": null + }, + "cpython-3.8.2-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 8, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-unknown-linux-musl-noopt-20200418T2309.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.8.2-windows-i686-none": { "name": "cpython", @@ -6957,7 +12333,8 @@ "patch": 2, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.8.2-windows-x86_64-none": { "name": "cpython", @@ -6969,7 +12346,34 @@ "patch": 2, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.8.2+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 8, + "patch": 2, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-apple-darwin-debug-20200418T2258.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.8.2+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 8, + "patch": 2, + "prerelease": "", + "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, + "variant": "debug" }, "cpython-3.7.9-darwin-x86_64-none": { "name": "cpython", @@ -6981,7 +12385,8 @@ "patch": 9, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.7.9-linux-x86_64-gnu": { "name": "cpython", @@ -6993,7 +12398,8 @@ "patch": 9, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-gnu-pgo-20200823T0036.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.7.9-linux-x86_64-musl": { "name": "cpython", @@ -7004,8 +12410,9 @@ "minor": 7, "patch": 9, "prerelease": "", - "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 + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-musl-noopt-20200823T0036.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.7.9-windows-i686-none": { "name": "cpython", @@ -7017,7 +12424,8 @@ "patch": 9, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.7.9-windows-x86_64-none": { "name": "cpython", @@ -7029,7 +12437,47 @@ "patch": 9, "prerelease": "", "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 + "sha256": null, + "variant": null + }, + "cpython-3.7.9+debug-darwin-x86_64-none": { + "name": "cpython", + "arch": "x86_64", + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 7, + "patch": 9, + "prerelease": "", + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.7.9-x86_64-apple-darwin-debug-20200823T2228.tar.zst", + "sha256": null, + "variant": "debug" + }, + "cpython-3.7.9+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 7, + "patch": 9, + "prerelease": "", + "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": null, + "variant": "debug" + }, + "cpython-3.7.9+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": "x86_64", + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 7, + "patch": 9, + "prerelease": "", + "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, + "variant": "debug" }, "cpython-3.7.7-darwin-x86_64-none": { "name": "cpython", @@ -7041,7 +12489,8 @@ "patch": 7, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.7.7-linux-x86_64-gnu": { "name": "cpython", @@ -7053,7 +12502,8 @@ "patch": 7, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-unknown-linux-gnu-pgo-20200518T0040.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.7.7-linux-x86_64-musl": { "name": "cpython", @@ -7064,8 +12514,9 @@ "minor": 7, "patch": 7, "prerelease": "", - "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 + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-unknown-linux-musl-noopt-20200518T0040.tar.zst", + "sha256": null, + "variant": null }, "cpython-3.7.7-windows-i686-none": { "name": "cpython", @@ -7077,7 +12528,8 @@ "patch": 7, "prerelease": "", "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 + "sha256": null, + "variant": null }, "cpython-3.7.7-windows-x86_64-none": { "name": "cpython", @@ -7089,43 +12541,47 @@ "patch": 7, "prerelease": "", "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 + "sha256": null, + "variant": null }, - "cpython-3.7.6-darwin-x86_64-none": { + "cpython-3.7.7+debug-darwin-x86_64-none": { "name": "cpython", "arch": "x86_64", "os": "darwin", "libc": "none", "major": 3, "minor": 7, - "patch": 6, + "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-macos-20200216T2344.tar.zst", - "sha256": null + "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200530/cpython-3.7.7-x86_64-apple-darwin-debug-20200530T1845.tar.zst", + "sha256": null, + "variant": "debug" }, - "cpython-3.7.6-linux-x86_64-gnu": { + "cpython-3.7.7+debug-linux-x86_64-gnu": { "name": "cpython", "arch": "x86_64", "os": "linux", "libc": "gnu", "major": 3, "minor": 7, - "patch": 6, + "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-linux64-20200216T2303.tar.zst", - "sha256": null + "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": null, + "variant": "debug" }, - "cpython-3.7.6-linux-x86_64-musl": { + "cpython-3.7.7+debug-linux-x86_64-musl": { "name": "cpython", "arch": "x86_64", "os": "linux", "libc": "musl", "major": 3, "minor": 7, - "patch": 6, + "patch": 7, "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200217/cpython-3.7.6-linux64-musl-20200218T0557.tar.zst", - "sha256": null + "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, + "variant": "debug" }, "cpython-3.7.6-windows-i686-none": { "name": "cpython", @@ -7137,7 +12593,8 @@ "patch": 6, "prerelease": "", "url": "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-x86-shared-pgo-20200217T0110.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "cpython-3.7.6-windows-x86_64-none": { "name": "cpython", @@ -7149,199 +12606,8 @@ "patch": 6, "prerelease": "", "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-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 5, - "prerelease": "", - "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", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 7, - "patch": 5, - "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-linux64-20191025T0506.tar.zst", - "sha256": null - }, - "cpython-3.7.5-linux-x86_64-musl": { - "name": "cpython", - "arch": "x86_64", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 7, - "patch": 5, - "prerelease": "", - "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-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 5, - "prerelease": "", - "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-windows-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "windows", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 5, - "prerelease": "", - "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-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 4, - "prerelease": "", - "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", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 7, - "patch": 4, - "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-linux64-20190817T0224.tar.zst", - "sha256": null - }, - "cpython-3.7.4-linux-x86_64-musl": { - "name": "cpython", - "arch": "x86_64", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 7, - "patch": 4, - "prerelease": "", - "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-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 4, - "prerelease": "", - "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-windows-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "windows", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 4, - "prerelease": "", - "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-darwin-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "darwin", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 3, - "prerelease": "", - "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", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 7, - "patch": 3, - "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-linux64-20190618T0324.tar.zst", - "sha256": null - }, - "cpython-3.7.3-linux-x86_64-musl": { - "name": "cpython", - "arch": "x86_64", - "os": "linux", - "libc": "musl", - "major": 3, - "minor": 7, - "patch": 3, - "prerelease": "", - "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-windows-i686-none": { - "name": "cpython", - "arch": "i686", - "os": "windows", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 3, - "prerelease": "", - "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-windows-x86_64-none": { - "name": "cpython", - "arch": "x86_64", - "os": "windows", - "libc": "none", - "major": 3, - "minor": 7, - "patch": 3, - "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-windows-amd64-20190618T0516.tar.zst", - "sha256": null - }, - "cpython-3.7.1-linux-x86_64-gnu": { - "name": "cpython", - "arch": "x86_64", - "os": "linux", - "libc": "gnu", - "major": 3, - "minor": 7, - "patch": 1, - "prerelease": "", - "url": "https://github.com/indygreg/python-build-standalone/releases/download/20181218/cpython-3.7.1-linux64-20181218T1905.tar.zst", - "sha256": null + "sha256": null, + "variant": null }, "pypy-3.10.14-darwin-aarch64-none": { "name": "pypy", @@ -7353,7 +12619,8 @@ "patch": 14, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_arm64.tar.bz2", - "sha256": "a050e25e8d686853dd5afc363e55625165825dacfb55f8753d8225ebe417cfd2" + "sha256": "a050e25e8d686853dd5afc363e55625165825dacfb55f8753d8225ebe417cfd2", + "variant": null }, "pypy-3.10.14-darwin-x86_64-none": { "name": "pypy", @@ -7365,7 +12632,8 @@ "patch": 14, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_x86_64.tar.bz2", - "sha256": "6c2c5f2300d7564e711421b4968abd63243cb96f76e363975dd648ebf4a362ee" + "sha256": "6c2c5f2300d7564e711421b4968abd63243cb96f76e363975dd648ebf4a362ee", + "variant": null }, "pypy-3.10.14-linux-aarch64-gnu": { "name": "pypy", @@ -7377,7 +12645,8 @@ "patch": 14, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.17-aarch64.tar.bz2", - "sha256": "53b6e5907df869c49e4eae7aca09fba16d150741097efb245892c1477d2395f2" + "sha256": "53b6e5907df869c49e4eae7aca09fba16d150741097efb245892c1477d2395f2", + "variant": null }, "pypy-3.10.14-linux-i686-gnu": { "name": "pypy", @@ -7389,7 +12658,8 @@ "patch": 14, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.17-linux32.tar.bz2", - "sha256": "e534110e1047da37c1d586c392f74de3424f871d906a2083de6d41f2a8cc9164" + "sha256": "e534110e1047da37c1d586c392f74de3424f871d906a2083de6d41f2a8cc9164", + "variant": null }, "pypy-3.10.14-linux-s390x-gnu": { "name": "pypy", @@ -7401,7 +12671,8 @@ "patch": 14, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.16-s390x.tar.bz2", - "sha256": "af97efe498a209ba18c7bc7d084164a9907fb3736588b6864955177e19d5216a" + "sha256": "af97efe498a209ba18c7bc7d084164a9907fb3736588b6864955177e19d5216a", + "variant": null }, "pypy-3.10.14-linux-x86_64-gnu": { "name": "pypy", @@ -7413,7 +12684,8 @@ "patch": 14, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.17-linux64.tar.bz2", - "sha256": "fdcdb9b24f1a7726003586503fdeb264fd68fc37fbfcea022dcfe825a7fee18b" + "sha256": "fdcdb9b24f1a7726003586503fdeb264fd68fc37fbfcea022dcfe825a7fee18b", + "variant": null }, "pypy-3.10.14-windows-x86_64-none": { "name": "pypy", @@ -7425,7 +12697,8 @@ "patch": 14, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.17-win64.zip", - "sha256": "cab794a03ddda26238c72942ea6f225612e0dc17c76cac6652da83a95024e6e8" + "sha256": "cab794a03ddda26238c72942ea6f225612e0dc17c76cac6652da83a95024e6e8", + "variant": null }, "pypy-3.10.13-darwin-aarch64-none": { "name": "pypy", @@ -7437,7 +12710,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.15-macos_arm64.tar.bz2", - "sha256": "d927c5105ea7880f7596fe459183e35cc17c853ef5105678b2ad62a8d000a548" + "sha256": "d927c5105ea7880f7596fe459183e35cc17c853ef5105678b2ad62a8d000a548", + "variant": null }, "pypy-3.10.13-darwin-x86_64-none": { "name": "pypy", @@ -7449,7 +12723,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.15-macos_x86_64.tar.bz2", - "sha256": "559b61ba7e7c5a5c23cef5370f1fab47ccdb939ac5d2b42b4bef091abe3f6964" + "sha256": "559b61ba7e7c5a5c23cef5370f1fab47ccdb939ac5d2b42b4bef091abe3f6964", + "variant": null }, "pypy-3.10.13-linux-aarch64-gnu": { "name": "pypy", @@ -7461,7 +12736,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.15-aarch64.tar.bz2", - "sha256": "52146fccaf64e87e71d178dda8de63c01577ec3923073dc69e1519622bcacb74" + "sha256": "52146fccaf64e87e71d178dda8de63c01577ec3923073dc69e1519622bcacb74", + "variant": null }, "pypy-3.10.13-linux-i686-gnu": { "name": "pypy", @@ -7473,7 +12749,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.15-linux32.tar.bz2", - "sha256": "75dd58c9abd8b9d78220373148355bc3119febcf27a2c781d64ad85e7232c4aa" + "sha256": "75dd58c9abd8b9d78220373148355bc3119febcf27a2c781d64ad85e7232c4aa", + "variant": null }, "pypy-3.10.13-linux-s390x-gnu": { "name": "pypy", @@ -7485,7 +12762,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.15-s390x.tar.bz2", - "sha256": "209e57596381e13c9914d1332f359dc4b78de06576739747eb797bdbf85062b8" + "sha256": "209e57596381e13c9914d1332f359dc4b78de06576739747eb797bdbf85062b8", + "variant": null }, "pypy-3.10.13-linux-x86_64-gnu": { "name": "pypy", @@ -7497,7 +12775,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.15-linux64.tar.bz2", - "sha256": "33c584e9a70a71afd0cb7dd8ba9996720b911b3b8ed0156aea298d4487ad22c3" + "sha256": "33c584e9a70a71afd0cb7dd8ba9996720b911b3b8ed0156aea298d4487ad22c3", + "variant": null }, "pypy-3.10.13-windows-x86_64-none": { "name": "pypy", @@ -7509,7 +12788,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.15-win64.zip", - "sha256": "b378b3ab1c3719aee0c3e5519e7bff93ff67b2d8aa987fe4f088b54382db676c" + "sha256": "b378b3ab1c3719aee0c3e5519e7bff93ff67b2d8aa987fe4f088b54382db676c", + "variant": null }, "pypy-3.10.12-darwin-aarch64-none": { "name": "pypy", @@ -7521,7 +12801,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2", - "sha256": "45671b1e9437f95ccd790af10dbeb57733cca1ed9661463b727d3c4f5caa7ba0" + "sha256": "45671b1e9437f95ccd790af10dbeb57733cca1ed9661463b727d3c4f5caa7ba0", + "variant": null }, "pypy-3.10.12-darwin-x86_64-none": { "name": "pypy", @@ -7533,7 +12814,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2", - "sha256": "dbc15d8570560d5f79366883c24bc42231a92855ac19a0f28cb0adeb11242666" + "sha256": "dbc15d8570560d5f79366883c24bc42231a92855ac19a0f28cb0adeb11242666", + "variant": null }, "pypy-3.10.12-linux-aarch64-gnu": { "name": "pypy", @@ -7545,7 +12827,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", - "sha256": "26208b5a134d9860a08f74cce60960005758e82dc5f0e3566a48ed863a1f16a1" + "sha256": "26208b5a134d9860a08f74cce60960005758e82dc5f0e3566a48ed863a1f16a1", + "variant": null }, "pypy-3.10.12-linux-i686-gnu": { "name": "pypy", @@ -7557,7 +12840,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux32.tar.bz2", - "sha256": "811667825ae58ada4b7c3d8bc1b5055b9f9d6a377e51aedfbe0727966603f60e" + "sha256": "811667825ae58ada4b7c3d8bc1b5055b9f9d6a377e51aedfbe0727966603f60e", + "variant": null }, "pypy-3.10.12-linux-s390x-gnu": { "name": "pypy", @@ -7569,7 +12853,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.12-s390x.tar.bz2", - "sha256": "043c13a585479428b463ab69575a088db74aadc16798d6e677d97f563585fee3" + "sha256": "043c13a585479428b463ab69575a088db74aadc16798d6e677d97f563585fee3", + "variant": null }, "pypy-3.10.12-linux-x86_64-gnu": { "name": "pypy", @@ -7581,7 +12866,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2", - "sha256": "6c577993160b6f5ee8cab73cd1a807affcefafe2f7441c87bd926c10505e8731" + "sha256": "6c577993160b6f5ee8cab73cd1a807affcefafe2f7441c87bd926c10505e8731", + "variant": null }, "pypy-3.10.12-windows-x86_64-none": { "name": "pypy", @@ -7593,7 +12879,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip", - "sha256": "8c3b1d34fb99100e230e94560410a38d450dc844effbee9ea183518e4aff595c" + "sha256": "8c3b1d34fb99100e230e94560410a38d450dc844effbee9ea183518e4aff595c", + "variant": null }, "pypy-3.9.19-darwin-aarch64-none": { "name": "pypy", @@ -7605,7 +12892,8 @@ "patch": 19, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2", - "sha256": "88f824e7a2d676440d09bc90fc959ae0fd3557d7e2f14bfbbe53d41d159a47fe" + "sha256": "88f824e7a2d676440d09bc90fc959ae0fd3557d7e2f14bfbbe53d41d159a47fe", + "variant": null }, "pypy-3.9.19-darwin-x86_64-none": { "name": "pypy", @@ -7617,7 +12905,8 @@ "patch": 19, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2", - "sha256": "fda015431621e7e5aa16359d114f2c45a77ed936992c1efff86302e768a6b21c" + "sha256": "fda015431621e7e5aa16359d114f2c45a77ed936992c1efff86302e768a6b21c", + "variant": null }, "pypy-3.9.19-linux-aarch64-gnu": { "name": "pypy", @@ -7629,7 +12918,8 @@ "patch": 19, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.16-aarch64.tar.bz2", - "sha256": "de3f2ed3581b30555ac0dd3e4df78a262ec736a36fb2e8f28259f8539b278ef4" + "sha256": "de3f2ed3581b30555ac0dd3e4df78a262ec736a36fb2e8f28259f8539b278ef4", + "variant": null }, "pypy-3.9.19-linux-i686-gnu": { "name": "pypy", @@ -7641,7 +12931,8 @@ "patch": 19, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.16-linux32.tar.bz2", - "sha256": "583b6d6dd4e8c07cbc04da04a7ec2bdfa6674825289c2378c5e018d5abe779ea" + "sha256": "583b6d6dd4e8c07cbc04da04a7ec2bdfa6674825289c2378c5e018d5abe779ea", + "variant": null }, "pypy-3.9.19-linux-s390x-gnu": { "name": "pypy", @@ -7653,7 +12944,8 @@ "patch": 19, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.16-s390x.tar.bz2", - "sha256": "7a56ebb27dba3110dc1ff52d8e0449cdb37fe5c2275f7faf11432e4e164833ba" + "sha256": "7a56ebb27dba3110dc1ff52d8e0449cdb37fe5c2275f7faf11432e4e164833ba", + "variant": null }, "pypy-3.9.19-linux-x86_64-gnu": { "name": "pypy", @@ -7665,7 +12957,8 @@ "patch": 19, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.16-linux64.tar.bz2", - "sha256": "16f9c5b808c848516e742986e826b833cdbeda09ad8764e8704595adbe791b23" + "sha256": "16f9c5b808c848516e742986e826b833cdbeda09ad8764e8704595adbe791b23", + "variant": null }, "pypy-3.9.19-windows-x86_64-none": { "name": "pypy", @@ -7677,7 +12970,8 @@ "patch": 19, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip", - "sha256": "06ec12a5e964dc0ad33e6f380185a4d295178dce6d6df512f508e7aee00a1323" + "sha256": "06ec12a5e964dc0ad33e6f380185a4d295178dce6d6df512f508e7aee00a1323", + "variant": null }, "pypy-3.9.18-darwin-aarch64-none": { "name": "pypy", @@ -7689,7 +12983,8 @@ "patch": 18, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.15-macos_arm64.tar.bz2", - "sha256": "300541c32125767a91b182b03d9cc4257f04971af32d747ecd4d62549d72acfd" + "sha256": "300541c32125767a91b182b03d9cc4257f04971af32d747ecd4d62549d72acfd", + "variant": null }, "pypy-3.9.18-darwin-x86_64-none": { "name": "pypy", @@ -7701,7 +12996,8 @@ "patch": 18, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.15-macos_x86_64.tar.bz2", - "sha256": "18ad7c9cb91c5e8ef9d40442b2fd1f6392ae113794c5b6b7d3a45e04f19edec6" + "sha256": "18ad7c9cb91c5e8ef9d40442b2fd1f6392ae113794c5b6b7d3a45e04f19edec6", + "variant": null }, "pypy-3.9.18-linux-aarch64-gnu": { "name": "pypy", @@ -7713,7 +13009,8 @@ "patch": 18, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.15-aarch64.tar.bz2", - "sha256": "03e35fcba290454bb0ccf7ee57fb42d1e63108d10d593776a382c0a2fe355de0" + "sha256": "03e35fcba290454bb0ccf7ee57fb42d1e63108d10d593776a382c0a2fe355de0", + "variant": null }, "pypy-3.9.18-linux-i686-gnu": { "name": "pypy", @@ -7725,7 +13022,8 @@ "patch": 18, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.15-linux32.tar.bz2", - "sha256": "c6209380977066c9e8b96e8258821c70f996004ce1bc8659ae83d4fd5a89ff5c" + "sha256": "c6209380977066c9e8b96e8258821c70f996004ce1bc8659ae83d4fd5a89ff5c", + "variant": null }, "pypy-3.9.18-linux-s390x-gnu": { "name": "pypy", @@ -7737,7 +13035,8 @@ "patch": 18, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.15-s390x.tar.bz2", - "sha256": "deeb5e54c36a0fd9cfefd16e63a0d5bed4f4a43e6bbc01c23f0ed8f7f1c0aaf3" + "sha256": "deeb5e54c36a0fd9cfefd16e63a0d5bed4f4a43e6bbc01c23f0ed8f7f1c0aaf3", + "variant": null }, "pypy-3.9.18-linux-x86_64-gnu": { "name": "pypy", @@ -7749,7 +13048,8 @@ "patch": 18, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.15-linux64.tar.bz2", - "sha256": "f062be307200bde434817e1620cebc13f563d6ab25309442c5f4d0f0d68f0912" + "sha256": "f062be307200bde434817e1620cebc13f563d6ab25309442c5f4d0f0d68f0912", + "variant": null }, "pypy-3.9.18-windows-x86_64-none": { "name": "pypy", @@ -7761,7 +13061,8 @@ "patch": 18, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.15-win64.zip", - "sha256": "a156dad8b58570597eaaabe05663f00f80c60bc11df4a9c46d0953b6c5eb9209" + "sha256": "a156dad8b58570597eaaabe05663f00f80c60bc11df4a9c46d0953b6c5eb9209", + "variant": null }, "pypy-3.9.17-darwin-aarch64-none": { "name": "pypy", @@ -7773,7 +13074,8 @@ "patch": 17, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_arm64.tar.bz2", - "sha256": "0e8a1a3468b9790c734ac698f5b00cc03fc16899ccc6ce876465fac0b83980e3" + "sha256": "0e8a1a3468b9790c734ac698f5b00cc03fc16899ccc6ce876465fac0b83980e3", + "variant": null }, "pypy-3.9.17-darwin-x86_64-none": { "name": "pypy", @@ -7785,7 +13087,8 @@ "patch": 17, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_x86_64.tar.bz2", - "sha256": "64f008ffa070c407e5ef46c8256b2e014de7196ea5d858385861254e7959f4eb" + "sha256": "64f008ffa070c407e5ef46c8256b2e014de7196ea5d858385861254e7959f4eb", + "variant": null }, "pypy-3.9.17-linux-aarch64-gnu": { "name": "pypy", @@ -7797,7 +13100,8 @@ "patch": 17, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.12-aarch64.tar.bz2", - "sha256": "e9327fb9edaf2ad91935d5b8563ec5ff24193bddb175c1acaaf772c025af1824" + "sha256": "e9327fb9edaf2ad91935d5b8563ec5ff24193bddb175c1acaaf772c025af1824", + "variant": null }, "pypy-3.9.17-linux-i686-gnu": { "name": "pypy", @@ -7809,7 +13113,8 @@ "patch": 17, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.12-linux32.tar.bz2", - "sha256": "aa04370d38f451683ccc817d76c2b3e0f471dbb879e0bd618d9affbdc9cd37a4" + "sha256": "aa04370d38f451683ccc817d76c2b3e0f471dbb879e0bd618d9affbdc9cd37a4", + "variant": null }, "pypy-3.9.17-linux-s390x-gnu": { "name": "pypy", @@ -7821,7 +13126,8 @@ "patch": 17, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.12-s390x.tar.bz2", - "sha256": "20d84658a6899bdd2ca35b00ead33a2f56cff2c40dce1af630466d27952f6d4f" + "sha256": "20d84658a6899bdd2ca35b00ead33a2f56cff2c40dce1af630466d27952f6d4f", + "variant": null }, "pypy-3.9.17-linux-x86_64-gnu": { "name": "pypy", @@ -7833,7 +13139,8 @@ "patch": 17, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.12-linux64.tar.bz2", - "sha256": "84c89b966fab2b58f451a482ee30ca7fec3350435bd0b9614615c61dc6da2390" + "sha256": "84c89b966fab2b58f451a482ee30ca7fec3350435bd0b9614615c61dc6da2390", + "variant": null }, "pypy-3.9.17-windows-x86_64-none": { "name": "pypy", @@ -7845,7 +13152,8 @@ "patch": 17, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.12-win64.zip", - "sha256": "0996054207b401aeacace1aa11bad82cfcb463838a1603c5f263626c47bbe0e6" + "sha256": "0996054207b401aeacace1aa11bad82cfcb463838a1603c5f263626c47bbe0e6", + "variant": null }, "pypy-3.9.16-darwin-aarch64-none": { "name": "pypy", @@ -7857,7 +13165,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_arm64.tar.bz2", - "sha256": "91ad7500f1a39531dbefa0b345a3dcff927ff9971654e8d2e9ef7c5ae311f57e" + "sha256": "91ad7500f1a39531dbefa0b345a3dcff927ff9971654e8d2e9ef7c5ae311f57e", + "variant": null }, "pypy-3.9.16-darwin-x86_64-none": { "name": "pypy", @@ -7869,7 +13178,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_x86_64.tar.bz2", - "sha256": "d33f40b207099872585afd71873575ca6ea638a27d823bc621238c5ae82542ed" + "sha256": "d33f40b207099872585afd71873575ca6ea638a27d823bc621238c5ae82542ed", + "variant": null }, "pypy-3.9.16-linux-aarch64-gnu": { "name": "pypy", @@ -7881,7 +13191,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.11-aarch64.tar.bz2", - "sha256": "09175dc652ed895d98e9ad63d216812bf3ee7e398d900a9bf9eb2906ba8302b9" + "sha256": "09175dc652ed895d98e9ad63d216812bf3ee7e398d900a9bf9eb2906ba8302b9", + "variant": null }, "pypy-3.9.16-linux-i686-gnu": { "name": "pypy", @@ -7893,7 +13204,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux32.tar.bz2", - "sha256": "0099d72c2897b229057bff7e2c343624aeabdc60d6fb43ca882bff082f1ffa48" + "sha256": "0099d72c2897b229057bff7e2c343624aeabdc60d6fb43ca882bff082f1ffa48", + "variant": null }, "pypy-3.9.16-linux-s390x-gnu": { "name": "pypy", @@ -7905,7 +13217,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.11-s390x.tar.bz2", - "sha256": "e1f30f2ddbe3f446ddacd79677b958d56c07463b20171fb2abf8f9a3178b79fc" + "sha256": "e1f30f2ddbe3f446ddacd79677b958d56c07463b20171fb2abf8f9a3178b79fc", + "variant": null }, "pypy-3.9.16-linux-x86_64-gnu": { "name": "pypy", @@ -7917,7 +13230,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2", - "sha256": "d506172ca11071274175d74e9c581c3166432d0179b036470e3b9e8d20eae581" + "sha256": "d506172ca11071274175d74e9c581c3166432d0179b036470e3b9e8d20eae581", + "variant": null }, "pypy-3.9.16-windows-x86_64-none": { "name": "pypy", @@ -7929,7 +13243,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.11-win64.zip", - "sha256": "57faad132d42d3e7a6406fcffafffe0b4f390cf0e2966abb8090d073c6edf405" + "sha256": "57faad132d42d3e7a6406fcffafffe0b4f390cf0e2966abb8090d073c6edf405", + "variant": null }, "pypy-3.9.15-darwin-aarch64-none": { "name": "pypy", @@ -7941,7 +13256,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.10-macos_arm64.tar.bz2", - "sha256": "e2a6bec7408e6497c7de8165aa4a1b15e2416aec4a72f2578f793fb06859ccba" + "sha256": "e2a6bec7408e6497c7de8165aa4a1b15e2416aec4a72f2578f793fb06859ccba", + "variant": null }, "pypy-3.9.15-darwin-x86_64-none": { "name": "pypy", @@ -7953,7 +13269,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.10-macos_x86_64.tar.bz2", - "sha256": "f90c8619b41e68ec9ffd7d5e913fe02e60843da43d3735b1c1bc75bcfe638d97" + "sha256": "f90c8619b41e68ec9ffd7d5e913fe02e60843da43d3735b1c1bc75bcfe638d97", + "variant": null }, "pypy-3.9.15-linux-aarch64-gnu": { "name": "pypy", @@ -7965,7 +13282,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.10-aarch64.tar.bz2", - "sha256": "657a04fd9a5a992a2f116a9e7e9132ea0c578721f59139c9fb2083775f71e514" + "sha256": "657a04fd9a5a992a2f116a9e7e9132ea0c578721f59139c9fb2083775f71e514", + "variant": null }, "pypy-3.9.15-linux-i686-gnu": { "name": "pypy", @@ -7977,7 +13295,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.10-linux32.tar.bz2", - "sha256": "b6db59613b9a1c0c1ab87bc103f52ee95193423882dc8a848b68850b8ba59cc5" + "sha256": "b6db59613b9a1c0c1ab87bc103f52ee95193423882dc8a848b68850b8ba59cc5", + "variant": null }, "pypy-3.9.15-linux-s390x-gnu": { "name": "pypy", @@ -7989,7 +13308,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.10-s390x.tar.bz2", - "sha256": "ca6525a540cf0c682d1592ae35d3fbc97559a97260e4b789255cc76dde7a14f0" + "sha256": "ca6525a540cf0c682d1592ae35d3fbc97559a97260e4b789255cc76dde7a14f0", + "variant": null }, "pypy-3.9.15-linux-x86_64-gnu": { "name": "pypy", @@ -8001,7 +13321,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.10-linux64.tar.bz2", - "sha256": "95cf99406179460d63ddbfe1ec870f889d05f7767ce81cef14b88a3a9e127266" + "sha256": "95cf99406179460d63ddbfe1ec870f889d05f7767ce81cef14b88a3a9e127266", + "variant": null }, "pypy-3.9.15-windows-x86_64-none": { "name": "pypy", @@ -8013,7 +13334,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.10-win64.zip", - "sha256": "07e18b7b24c74af9730dfaab16e24b22ef94ea9a4b64cbb2c0d80610a381192a" + "sha256": "07e18b7b24c74af9730dfaab16e24b22ef94ea9a4b64cbb2c0d80610a381192a", + "variant": null }, "pypy-3.9.12-darwin-x86_64-none": { "name": "pypy", @@ -8025,7 +13347,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.9-osx64.tar.bz2", - "sha256": "59c8852168b2b1ba1f0211ff043c678760380d2f9faf2f95042a8878554dbc25" + "sha256": "59c8852168b2b1ba1f0211ff043c678760380d2f9faf2f95042a8878554dbc25", + "variant": null }, "pypy-3.9.12-linux-aarch64-gnu": { "name": "pypy", @@ -8037,7 +13360,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.9-aarch64.tar.bz2", - "sha256": "2e1ae193d98bc51439642a7618d521ea019f45b8fb226940f7e334c548d2b4b9" + "sha256": "2e1ae193d98bc51439642a7618d521ea019f45b8fb226940f7e334c548d2b4b9", + "variant": null }, "pypy-3.9.12-linux-i686-gnu": { "name": "pypy", @@ -8049,7 +13373,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.9-linux32.tar.bz2", - "sha256": "0de4b9501cf28524cdedcff5052deee9ea4630176a512bdc408edfa30914bae7" + "sha256": "0de4b9501cf28524cdedcff5052deee9ea4630176a512bdc408edfa30914bae7", + "variant": null }, "pypy-3.9.12-linux-s390x-gnu": { "name": "pypy", @@ -8061,7 +13386,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.9-s390x.tar.bz2", - "sha256": "774dca83bcb4403fb99b3d155e7bd572ef8c52b9fe87a657109f64e75ad71732" + "sha256": "774dca83bcb4403fb99b3d155e7bd572ef8c52b9fe87a657109f64e75ad71732", + "variant": null }, "pypy-3.9.12-linux-x86_64-gnu": { "name": "pypy", @@ -8073,7 +13399,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.9-linux64.tar.bz2", - "sha256": "46818cb3d74b96b34787548343d266e2562b531ddbaf330383ba930ff1930ed5" + "sha256": "46818cb3d74b96b34787548343d266e2562b531ddbaf330383ba930ff1930ed5", + "variant": null }, "pypy-3.9.12-windows-x86_64-none": { "name": "pypy", @@ -8085,7 +13412,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.9-win64.zip", - "sha256": "be48ab42f95c402543a7042c999c9433b17e55477c847612c8733a583ca6dff5" + "sha256": "be48ab42f95c402543a7042c999c9433b17e55477c847612c8733a583ca6dff5", + "variant": null }, "pypy-3.9.10-darwin-x86_64-none": { "name": "pypy", @@ -8097,7 +13425,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.8-osx64.tar.bz2", - "sha256": "95bd88ac8d6372cd5b7b5393de7b7d5c615a0c6e42fdb1eb67f2d2d510965aee" + "sha256": "95bd88ac8d6372cd5b7b5393de7b7d5c615a0c6e42fdb1eb67f2d2d510965aee", + "variant": null }, "pypy-3.9.10-linux-aarch64-gnu": { "name": "pypy", @@ -8109,7 +13438,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.8-aarch64-portable.tar.bz2", - "sha256": "b7282bc4484bceae5bc4cc04e05ee4faf51cb624c8fc7a69d92e5fdf0d0c96aa" + "sha256": "b7282bc4484bceae5bc4cc04e05ee4faf51cb624c8fc7a69d92e5fdf0d0c96aa", + "variant": null }, "pypy-3.9.10-linux-i686-gnu": { "name": "pypy", @@ -8121,7 +13451,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.8-linux32.tar.bz2", - "sha256": "a0d18e4e73cc655eb02354759178b8fb161d3e53b64297d05e2fff91f7cf862d" + "sha256": "a0d18e4e73cc655eb02354759178b8fb161d3e53b64297d05e2fff91f7cf862d", + "variant": null }, "pypy-3.9.10-linux-s390x-gnu": { "name": "pypy", @@ -8133,7 +13464,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.8-s390x.tar.bz2", - "sha256": "37b596bfe76707ead38ffb565629697e9b6fa24e722acc3c632b41ec624f5d95" + "sha256": "37b596bfe76707ead38ffb565629697e9b6fa24e722acc3c632b41ec624f5d95", + "variant": null }, "pypy-3.9.10-linux-x86_64-gnu": { "name": "pypy", @@ -8145,7 +13477,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.8-linux64.tar.bz2", - "sha256": "129a055032bba700cd1d0acacab3659cf6b7180e25b1b2f730e792f06d5b3010" + "sha256": "129a055032bba700cd1d0acacab3659cf6b7180e25b1b2f730e792f06d5b3010", + "variant": null }, "pypy-3.9.10-windows-x86_64-none": { "name": "pypy", @@ -8157,7 +13490,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.9-v7.3.8-win64.zip", - "sha256": "c1b2e4cde2dcd1208d41ef7b7df8e5c90564a521e7a5db431673da335a1ba697" + "sha256": "c1b2e4cde2dcd1208d41ef7b7df8e5c90564a521e7a5db431673da335a1ba697", + "variant": null }, "pypy-3.8.16-darwin-aarch64-none": { "name": "pypy", @@ -8169,7 +13503,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2", - "sha256": "78cdc79ff964c4bfd13eb45a7d43a011cbe8d8b513323d204891f703fdc4fa1a" + "sha256": "78cdc79ff964c4bfd13eb45a7d43a011cbe8d8b513323d204891f703fdc4fa1a", + "variant": null }, "pypy-3.8.16-darwin-x86_64-none": { "name": "pypy", @@ -8181,7 +13516,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2", - "sha256": "194ca0b4d91ae409a9cb1a59eb7572d7affa8a451ea3daf26539aa515443433a" + "sha256": "194ca0b4d91ae409a9cb1a59eb7572d7affa8a451ea3daf26539aa515443433a", + "variant": null }, "pypy-3.8.16-linux-aarch64-gnu": { "name": "pypy", @@ -8193,7 +13529,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.11-aarch64.tar.bz2", - "sha256": "9a2fa0b8d92b7830aa31774a9a76129b0ff81afbd22cd5c41fbdd9119e859f55" + "sha256": "9a2fa0b8d92b7830aa31774a9a76129b0ff81afbd22cd5c41fbdd9119e859f55", + "variant": null }, "pypy-3.8.16-linux-i686-gnu": { "name": "pypy", @@ -8205,7 +13542,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux32.tar.bz2", - "sha256": "a79b31fce8f5bc1f9940b6777134189a1d3d18bda4b1c830384cda90077c9176" + "sha256": "a79b31fce8f5bc1f9940b6777134189a1d3d18bda4b1c830384cda90077c9176", + "variant": null }, "pypy-3.8.16-linux-s390x-gnu": { "name": "pypy", @@ -8217,7 +13555,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.11-s390x.tar.bz2", - "sha256": "eab7734d86d96549866f1cba67f4f9c73c989f6a802248beebc504080d4c3fcd" + "sha256": "eab7734d86d96549866f1cba67f4f9c73c989f6a802248beebc504080d4c3fcd", + "variant": null }, "pypy-3.8.16-linux-x86_64-gnu": { "name": "pypy", @@ -8229,7 +13568,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux64.tar.bz2", - "sha256": "470330e58ac105c094041aa07bb05676b06292bc61409e26f5c5593ebb2292d9" + "sha256": "470330e58ac105c094041aa07bb05676b06292bc61409e26f5c5593ebb2292d9", + "variant": null }, "pypy-3.8.16-windows-x86_64-none": { "name": "pypy", @@ -8241,7 +13581,8 @@ "patch": 16, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip", - "sha256": "0f46fb6df32941ea016f77cfd7e9b426d5ac25a2af2453414df66103941c8435" + "sha256": "0f46fb6df32941ea016f77cfd7e9b426d5ac25a2af2453414df66103941c8435", + "variant": null }, "pypy-3.8.15-darwin-aarch64-none": { "name": "pypy", @@ -8253,7 +13594,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.10-macos_arm64.tar.bz2", - "sha256": "6cb1429371e4854b718148a509d80143f801e3abfc72fef58d88aeeee1e98f9e" + "sha256": "6cb1429371e4854b718148a509d80143f801e3abfc72fef58d88aeeee1e98f9e", + "variant": null }, "pypy-3.8.15-darwin-x86_64-none": { "name": "pypy", @@ -8265,7 +13607,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.10-macos_x86_64.tar.bz2", - "sha256": "399eb1ce4c65f62f6a096b7c273536601b7695e3c0dc0457393a659b95b7615b" + "sha256": "399eb1ce4c65f62f6a096b7c273536601b7695e3c0dc0457393a659b95b7615b", + "variant": null }, "pypy-3.8.15-linux-aarch64-gnu": { "name": "pypy", @@ -8277,7 +13620,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.10-aarch64.tar.bz2", - "sha256": "e4caa1a545f22cfee87d5b9aa6f8852347f223643ad7d2562e0b2a2f4663ad98" + "sha256": "e4caa1a545f22cfee87d5b9aa6f8852347f223643ad7d2562e0b2a2f4663ad98", + "variant": null }, "pypy-3.8.15-linux-i686-gnu": { "name": "pypy", @@ -8289,7 +13633,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.10-linux32.tar.bz2", - "sha256": "b70ed7fdc73a74ebdc04f07439f7bad1a849aaca95e26b4a74049d0e483f071c" + "sha256": "b70ed7fdc73a74ebdc04f07439f7bad1a849aaca95e26b4a74049d0e483f071c", + "variant": null }, "pypy-3.8.15-linux-s390x-gnu": { "name": "pypy", @@ -8301,7 +13646,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.10-s390x.tar.bz2", - "sha256": "c294f8e815158388628fe77ac5b8ad6cd93c8db1359091fa02d41cf6da4d61a1" + "sha256": "c294f8e815158388628fe77ac5b8ad6cd93c8db1359091fa02d41cf6da4d61a1", + "variant": null }, "pypy-3.8.15-linux-x86_64-gnu": { "name": "pypy", @@ -8313,7 +13659,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.10-linux64.tar.bz2", - "sha256": "ceef6496fd4ab1c99e3ec22ce657b8f10f8bb77a32427fadfb5e1dd943806011" + "sha256": "ceef6496fd4ab1c99e3ec22ce657b8f10f8bb77a32427fadfb5e1dd943806011", + "variant": null }, "pypy-3.8.15-windows-x86_64-none": { "name": "pypy", @@ -8325,7 +13672,8 @@ "patch": 15, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.10-win64.zip", - "sha256": "362dd624d95bd64743190ea2539b97452ecb3d53ea92ceb2fbe9f48dc60e6b8f" + "sha256": "362dd624d95bd64743190ea2539b97452ecb3d53ea92ceb2fbe9f48dc60e6b8f", + "variant": null }, "pypy-3.8.13-darwin-x86_64-none": { "name": "pypy", @@ -8337,7 +13685,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.9-osx64.tar.bz2", - "sha256": "91a5c2c1facd5a4931a8682b7d792f7cf4f2ba25cd2e7e44e982139a6d5e4840" + "sha256": "91a5c2c1facd5a4931a8682b7d792f7cf4f2ba25cd2e7e44e982139a6d5e4840", + "variant": null }, "pypy-3.8.13-linux-aarch64-gnu": { "name": "pypy", @@ -8349,7 +13698,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.9-aarch64.tar.bz2", - "sha256": "5e124455e207425e80731dff317f0432fa0aba1f025845ffca813770e2447e32" + "sha256": "5e124455e207425e80731dff317f0432fa0aba1f025845ffca813770e2447e32", + "variant": null }, "pypy-3.8.13-linux-i686-gnu": { "name": "pypy", @@ -8361,7 +13711,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.9-linux32.tar.bz2", - "sha256": "4b261516c6c59078ab0c8bd7207327a1b97057b4ec1714ed5e79a026f9efd492" + "sha256": "4b261516c6c59078ab0c8bd7207327a1b97057b4ec1714ed5e79a026f9efd492", + "variant": null }, "pypy-3.8.13-linux-s390x-gnu": { "name": "pypy", @@ -8373,7 +13724,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.9-s390x.tar.bz2", - "sha256": "c6177a0016c9145c7b99fddb5d74cc2e518ccdb216a6deb51ef6a377510cc930" + "sha256": "c6177a0016c9145c7b99fddb5d74cc2e518ccdb216a6deb51ef6a377510cc930", + "variant": null }, "pypy-3.8.13-linux-x86_64-gnu": { "name": "pypy", @@ -8385,7 +13737,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.9-linux64.tar.bz2", - "sha256": "08be25ec82fc5d23b78563eda144923517daba481a90af0ace7a047c9c9a3c34" + "sha256": "08be25ec82fc5d23b78563eda144923517daba481a90af0ace7a047c9c9a3c34", + "variant": null }, "pypy-3.8.13-windows-x86_64-none": { "name": "pypy", @@ -8397,7 +13750,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.9-win64.zip", - "sha256": "05022baaa55db2b60880f2422312d9e4025e1267303ac57f33e8253559d0be88" + "sha256": "05022baaa55db2b60880f2422312d9e4025e1267303ac57f33e8253559d0be88", + "variant": null }, "pypy-3.8.12-darwin-x86_64-none": { "name": "pypy", @@ -8409,7 +13763,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.8-osx64.tar.bz2", - "sha256": "de1b283ff112d76395c0162a1cf11528e192bdc230ee3f1b237f7694c7518dee" + "sha256": "de1b283ff112d76395c0162a1cf11528e192bdc230ee3f1b237f7694c7518dee", + "variant": null }, "pypy-3.8.12-linux-aarch64-gnu": { "name": "pypy", @@ -8421,7 +13776,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.8-aarch64-portable.tar.bz2", - "sha256": "0210536e9f1841ba283c13b04783394050837bb3e6f4091c9f1bd9c7f2b94b55" + "sha256": "0210536e9f1841ba283c13b04783394050837bb3e6f4091c9f1bd9c7f2b94b55", + "variant": null }, "pypy-3.8.12-linux-i686-gnu": { "name": "pypy", @@ -8433,7 +13789,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.8-linux32.tar.bz2", - "sha256": "bea4b275decd492af6462157d293dd6fcf08a949859f8aec0959537b40afd032" + "sha256": "bea4b275decd492af6462157d293dd6fcf08a949859f8aec0959537b40afd032", + "variant": null }, "pypy-3.8.12-linux-s390x-gnu": { "name": "pypy", @@ -8445,7 +13802,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.8-s390x.tar.bz2", - "sha256": "ad53d373d6e275a41ca64da7d88afb6a17e48e7bfb2a6fff92daafdc06da6b90" + "sha256": "ad53d373d6e275a41ca64da7d88afb6a17e48e7bfb2a6fff92daafdc06da6b90", + "variant": null }, "pypy-3.8.12-linux-x86_64-gnu": { "name": "pypy", @@ -8457,7 +13815,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.8-linux64.tar.bz2", - "sha256": "089f8e3e357d6130815964ddd3507c13bd53e4976ccf0a89b5c36a9a6775a188" + "sha256": "089f8e3e357d6130815964ddd3507c13bd53e4976ccf0a89b5c36a9a6775a188", + "variant": null }, "pypy-3.8.12-windows-x86_64-none": { "name": "pypy", @@ -8469,7 +13828,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.8-v7.3.8-win64.zip", - "sha256": "0894c468e7de758c509a602a28ef0ba4fbf197ccdf946c7853a7283d9bb2a345" + "sha256": "0894c468e7de758c509a602a28ef0ba4fbf197ccdf946c7853a7283d9bb2a345", + "variant": null }, "pypy-3.7.13-darwin-x86_64-none": { "name": "pypy", @@ -8481,7 +13841,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2", - "sha256": "12d92f578a200d50959e55074b20f29f93c538943e9a6e6522df1a1cc9cef542" + "sha256": "12d92f578a200d50959e55074b20f29f93c538943e9a6e6522df1a1cc9cef542", + "variant": null }, "pypy-3.7.13-linux-aarch64-gnu": { "name": "pypy", @@ -8493,7 +13854,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.9-aarch64.tar.bz2", - "sha256": "dfc62f2c453fb851d10a1879c6e75c31ffebbf2a44d181bb06fcac4750d023fc" + "sha256": "dfc62f2c453fb851d10a1879c6e75c31ffebbf2a44d181bb06fcac4750d023fc", + "variant": null }, "pypy-3.7.13-linux-i686-gnu": { "name": "pypy", @@ -8505,7 +13867,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux32.tar.bz2", - "sha256": "3398cece0167b81baa219c9cd54a549443d8c0a6b553ec8ec13236281e0d86cd" + "sha256": "3398cece0167b81baa219c9cd54a549443d8c0a6b553ec8ec13236281e0d86cd", + "variant": null }, "pypy-3.7.13-linux-s390x-gnu": { "name": "pypy", @@ -8517,7 +13880,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.9-s390x.tar.bz2", - "sha256": "fcab3b9e110379948217cf592229542f53c33bfe881006f95ce30ac815a6df48" + "sha256": "fcab3b9e110379948217cf592229542f53c33bfe881006f95ce30ac815a6df48", + "variant": null }, "pypy-3.7.13-linux-x86_64-gnu": { "name": "pypy", @@ -8529,7 +13893,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux64.tar.bz2", - "sha256": "c58195124d807ecc527499ee19bc511ed753f4f2e418203ca51bc7e3b124d5d1" + "sha256": "c58195124d807ecc527499ee19bc511ed753f4f2e418203ca51bc7e3b124d5d1", + "variant": null }, "pypy-3.7.13-windows-x86_64-none": { "name": "pypy", @@ -8541,7 +13906,8 @@ "patch": 13, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip", - "sha256": "8acb184b48fb3c854de0662e4d23a66b90e73b1ab73a86695022c12c745d8b00" + "sha256": "8acb184b48fb3c854de0662e4d23a66b90e73b1ab73a86695022c12c745d8b00", + "variant": null }, "pypy-3.7.12-darwin-x86_64-none": { "name": "pypy", @@ -8553,7 +13919,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.8-osx64.tar.bz2", - "sha256": "76b8eef5b059a7e478f525615482d2a6e9feb83375e3f63c16381d80521a693f" + "sha256": "76b8eef5b059a7e478f525615482d2a6e9feb83375e3f63c16381d80521a693f", + "variant": null }, "pypy-3.7.12-linux-aarch64-gnu": { "name": "pypy", @@ -8565,7 +13932,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.8-aarch64-portable.tar.bz2", - "sha256": "639c76f128a856747aee23a34276fa101a7a157ea81e76394fbaf80b97dcf2f2" + "sha256": "639c76f128a856747aee23a34276fa101a7a157ea81e76394fbaf80b97dcf2f2", + "variant": null }, "pypy-3.7.12-linux-i686-gnu": { "name": "pypy", @@ -8577,7 +13945,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.8-linux32.tar.bz2", - "sha256": "38429ec6ea1aca391821ee4fbda7358ae86de4600146643f2af2fe2c085af839" + "sha256": "38429ec6ea1aca391821ee4fbda7358ae86de4600146643f2af2fe2c085af839", + "variant": null }, "pypy-3.7.12-linux-s390x-gnu": { "name": "pypy", @@ -8589,7 +13958,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.8-s390x.tar.bz2", - "sha256": "5c2cd3f7cf04cb96f6bcc6b02e271f5d7275867763978e66651b8d1605ef3141" + "sha256": "5c2cd3f7cf04cb96f6bcc6b02e271f5d7275867763978e66651b8d1605ef3141", + "variant": null }, "pypy-3.7.12-linux-x86_64-gnu": { "name": "pypy", @@ -8601,7 +13971,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.8-linux64.tar.bz2", - "sha256": "409085db79a6d90bfcf4f576dca1538498e65937acfbe03bd4909bdc262ff378" + "sha256": "409085db79a6d90bfcf4f576dca1538498e65937acfbe03bd4909bdc262ff378", + "variant": null }, "pypy-3.7.12-windows-x86_64-none": { "name": "pypy", @@ -8613,7 +13984,8 @@ "patch": 12, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.8-win64.zip", - "sha256": "96df67492bc8d62b2e71dddf5f6c58965a26cac9799c5f4081401af0494b3bcc" + "sha256": "96df67492bc8d62b2e71dddf5f6c58965a26cac9799c5f4081401af0494b3bcc", + "variant": null }, "pypy-3.7.10-darwin-x86_64-none": { "name": "pypy", @@ -8625,7 +13997,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.5-osx64.tar.bz2", - "sha256": "b3a7d3099ad83de7c267bb79ae609d5ce73b01800578ffd91ba7e221b13f80db" + "sha256": "b3a7d3099ad83de7c267bb79ae609d5ce73b01800578ffd91ba7e221b13f80db", + "variant": null }, "pypy-3.7.10-linux-aarch64-gnu": { "name": "pypy", @@ -8637,7 +14010,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.5-aarch64.tar.bz2", - "sha256": "85d83093b3ef5b863f641bc4073d057cc98bb821e16aa9361a5ff4898e70e8ee" + "sha256": "85d83093b3ef5b863f641bc4073d057cc98bb821e16aa9361a5ff4898e70e8ee", + "variant": null }, "pypy-3.7.10-linux-i686-gnu": { "name": "pypy", @@ -8649,7 +14023,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.5-linux32.tar.bz2", - "sha256": "3dd8b565203d372829e53945c599296fa961895130342ea13791b17c84ed06c4" + "sha256": "3dd8b565203d372829e53945c599296fa961895130342ea13791b17c84ed06c4", + "variant": null }, "pypy-3.7.10-linux-s390x-gnu": { "name": "pypy", @@ -8661,7 +14036,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.5-s390x.tar.bz2", - "sha256": "dffdf5d73613be2c6809dc1a3cf3ee6ac2f3af015180910247ff24270b532ed5" + "sha256": "dffdf5d73613be2c6809dc1a3cf3ee6ac2f3af015180910247ff24270b532ed5", + "variant": null }, "pypy-3.7.10-linux-x86_64-gnu": { "name": "pypy", @@ -8673,7 +14049,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.5-linux64.tar.bz2", - "sha256": "9000db3e87b54638e55177e68cbeb30a30fe5d17b6be48a9eb43d65b3ebcfc26" + "sha256": "9000db3e87b54638e55177e68cbeb30a30fe5d17b6be48a9eb43d65b3ebcfc26", + "variant": null }, "pypy-3.7.10-windows-x86_64-none": { "name": "pypy", @@ -8685,7 +14062,8 @@ "patch": 10, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.5-win64.zip", - "sha256": "072bd22427178dc4e65d961f50281bd2f56e11c4e4d9f16311c703f69f46ae24" + "sha256": "072bd22427178dc4e65d961f50281bd2f56e11c4e4d9f16311c703f69f46ae24", + "variant": null }, "pypy-3.7.9-darwin-x86_64-none": { "name": "pypy", @@ -8697,7 +14075,8 @@ "patch": 9, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.3-osx64.tar.bz2", - "sha256": "d72b27d5bb60813273f14f07378a08822186a66e216c5d1a768ad295b582438d" + "sha256": "d72b27d5bb60813273f14f07378a08822186a66e216c5d1a768ad295b582438d", + "variant": null }, "pypy-3.7.9-linux-aarch64-gnu": { "name": "pypy", @@ -8709,7 +14088,8 @@ "patch": 9, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.3-aarch64.tar.bz2", - "sha256": "ee4aa041558b58de6063dd6df93b3def221c4ca4c900d6a9db5b1b52135703a8" + "sha256": "ee4aa041558b58de6063dd6df93b3def221c4ca4c900d6a9db5b1b52135703a8", + "variant": null }, "pypy-3.7.9-linux-i686-gnu": { "name": "pypy", @@ -8721,7 +14101,8 @@ "patch": 9, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.3-linux32.tar.bz2", - "sha256": "7d81b8e9fcd07c067cfe2f519ab770ec62928ee8787f952cadf2d2786246efc8" + "sha256": "7d81b8e9fcd07c067cfe2f519ab770ec62928ee8787f952cadf2d2786246efc8", + "variant": null }, "pypy-3.7.9-linux-s390x-gnu": { "name": "pypy", @@ -8733,7 +14114,8 @@ "patch": 9, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.3-s390x.tar.bz2", - "sha256": "92000d90b9a37f2e9cb7885f2a872adfa9e48e74bf7f84a8b8185c8181f0502d" + "sha256": "92000d90b9a37f2e9cb7885f2a872adfa9e48e74bf7f84a8b8185c8181f0502d", + "variant": null }, "pypy-3.7.9-linux-x86_64-gnu": { "name": "pypy", @@ -8745,7 +14127,8 @@ "patch": 9, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.3-linux64.tar.bz2", - "sha256": "37e2804c4661c86c857d709d28c7de716b000d31e89766599fdf5a98928b7096" + "sha256": "37e2804c4661c86c857d709d28c7de716b000d31e89766599fdf5a98928b7096", + "variant": null }, "pypy-3.7.9-windows-i686-none": { "name": "pypy", @@ -8757,6 +14140,7 @@ "patch": 9, "prerelease": "", "url": "https://downloads.python.org/pypy/pypy3.7-v7.3.3-win32.zip", - "sha256": "a282ce40aa4f853e877a5dbb38f0a586a29e563ae9ba82fd50c7e5dc465fb649" + "sha256": "a282ce40aa4f853e877a5dbb38f0a586a29e563ae9ba82fd50c7e5dc465fb649", + "variant": null } } \ No newline at end of file diff --git a/crates/uv-python/fetch-download-metadata.py b/crates/uv-python/fetch-download-metadata.py index ea35a1886..c3ad9b814 100755 --- a/crates/uv-python/fetch-download-metadata.py +++ b/crates/uv-python/fetch-download-metadata.py @@ -50,7 +50,7 @@ import json import logging import os import re -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import StrEnum from pathlib import Path from typing import Generator, Iterable, NamedTuple, Self @@ -106,6 +106,11 @@ class ImplementationName(StrEnum): PYPY = "pypy" +class Variant(StrEnum): + FREETHREADED = "freethreaded" + DEBUG = "debug" + + @dataclass class PythonDownload: version: Version @@ -115,9 +120,14 @@ class PythonDownload: filename: str url: str sha256: str | None = None + build_options: list[str] = field(default_factory=list) + variant: Variant | None = None def key(self) -> str: - return f"{self.implementation}-{self.version}-{self.triple.platform}-{self.triple.arch}-{self.triple.libc}" + if self.variant: + return f"{self.implementation}-{self.version}+{self.variant}-{self.triple.platform}-{self.triple.arch}-{self.triple.libc}" + else: + return f"{self.implementation}-{self.version}-{self.triple.platform}-{self.triple.arch}-{self.triple.libc}" class Finder: @@ -141,13 +151,6 @@ class CPythonFinder(Finder): "shared-pgo", "shared-noopt", "static-noopt", - "pgo+lto", - "pgo", - "lto", - "debug", - ] - HIDDEN_FLAVORS = [ - "noopt", ] SPECIAL_TRIPLES = { "macos": "x86_64-apple-darwin", @@ -167,24 +170,28 @@ class CPythonFinder(Finder): _filename_re = re.compile( r"""(?x) ^ - cpython-(?P\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?) - (?:\+\d+)? - -(?P.*?) - (?:-[\dT]+)?\.tar\.(?:gz|zst) + cpython- + (?P\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?)(?:\+\d+)?\+ + (?P\d+)- + (?P[a-z\d_]+-[a-z\d]+(?>-[a-z\d]+)?-[a-z\d]+)- + (?:(?P.+)-)? + (?P[a-z_]+)? + \.tar\.(?:gz|zst) $ - """ + """ ) - _flavor_re = re.compile( - r"""(?x)^(.*?)-(%s)$""" - % ( - "|".join( - map( - re.escape, - sorted(FLAVOR_PREFERENCES + HIDDEN_FLAVORS, key=len, reverse=True), - ) - ) - ) + _legacy_filename_re = re.compile( + r"""(?x) + ^ + cpython- + (?P\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?)(?:\+\d+)?- + (?P[a-z\d_-]+)- + (?P(debug|pgo|noopt|lto|pgo\+lto))?- + (?P[a-zA-z\d]+) + \.tar\.(?:gz|zst) + $ + """ ) def __init__(self, client: httpx.AsyncClient): @@ -197,7 +204,7 @@ class CPythonFinder(Finder): async def _fetch_downloads(self, pages: int = 100) -> list[PythonDownload]: """Fetch all the indygreg downloads from the release API.""" - results: dict[Version, list[PythonDownload]] = {} + downloads_by_version: dict[Version, list[PythonDownload]] = {} # Collect all available Python downloads for page in range(1, pages + 1): @@ -213,24 +220,40 @@ class CPythonFinder(Finder): download = self._parse_download_url(url) if download is None: continue - results.setdefault(download.version, []).append(download) + logging.debug("Found %s (%s)", download.key(), download.filename) + downloads_by_version.setdefault(download.version, []).append( + download + ) - # Collapse CPython variants to a single URL flavor per triple + # Collapse CPython variants to a single flavor per triple and variant downloads = [] - for choices in results.values(): - flavors: dict[PlatformTriple, tuple[PythonDownload, int]] = {} - for choice in choices: - priority = self._get_flavor_priority(choice.flavor) - existing = flavors.get(choice.triple) + for version_downloads in downloads_by_version.values(): + selected: dict[ + tuple[PlatformTriple, Variant | None], + tuple[PythonDownload, tuple[int, int]], + ] = {} + for download in version_downloads: + priority = self._get_priority(download) + existing = selected.get((download.triple, download.variant)) if existing: - _, existing_priority = existing + existing_download, existing_priority = existing # Skip if we have a flavor with higher priority already (indicated by a smaller value) if priority >= existing_priority: + logging.debug( + "Skipping %s (%s): lower priority than %s (%s)", + download.key(), + download.flavor, + existing_download.key(), + existing_download.flavor, + ) continue - flavors[choice.triple] = (choice, priority) + selected[(download.triple, download.variant)] = ( + download, + priority, + ) # Drop the priorities - downloads.extend([choice for choice, _ in flavors.values()]) + downloads.extend([download for download, _ in selected.values()]) return downloads @@ -286,25 +309,31 @@ class CPythonFinder(Finder): return None filename = unquote(url.rsplit("/", maxsplit=1)[-1]) - match = self._filename_re.match(filename) + match = self._filename_re.match(filename) or self._legacy_filename_re.match( + filename + ) if match is None: + logging.debug("Skipping %s: no regex match", filename) return None - version, triple = match.groups() - if triple.endswith("-full"): - triple = triple[:-5] + groups = match.groupdict() + version = groups["ver"] + triple = groups["triple"] + build_options = groups.get("build_options") + flavor = groups.get("flavor", "full") - match = self._flavor_re.match(triple) - if match is not None: - triple, flavor = match.groups() + build_options = build_options.split("+") if build_options else [] + variant: Variant | None + for variant in Variant: + if variant in build_options: + break else: - flavor = "" - if flavor in self.HIDDEN_FLAVORS: - return None + variant = None version = Version.from_str(version) triple = self._normalize_triple(triple) if triple is None: + # Skip is logged in `_normalize_triple` return None return PythonDownload( @@ -314,6 +343,8 @@ class CPythonFinder(Finder): implementation=self.implementation, filename=filename, url=url, + build_options=build_options, + variant=variant, ) def _normalize_triple(self, triple: str) -> PlatformTriple | None: @@ -346,13 +377,29 @@ class CPythonFinder(Finder): def _normalize_os(self, os: str) -> str: return os - def _get_flavor_priority(self, flavor: str) -> int: - """Returns the priority of a flavor. Lower is better.""" + def _get_priority(self, download: PythonDownload) -> tuple[int, int]: + """ + Returns the priority of a download, a lower score is better. + """ + flavor_priority = self._flavor_priority(download.flavor) + build_option_priority = self._build_option_priority(download.build_options) + return (flavor_priority, build_option_priority) + + def _flavor_priority(self, flavor: str) -> int: try: - pref = self.FLAVOR_PREFERENCES.index(flavor) + priority = self.FLAVOR_PREFERENCES.index(flavor) except ValueError: - pref = len(self.FLAVOR_PREFERENCES) + 1 - return pref + priority = len(self.FLAVOR_PREFERENCES) + 1 + return priority + + def _build_option_priority(self, build_options: list[str]) -> int: + # Prefer optimized builds + return -1 * sum( + ( + "lgo" in build_options, + "pgo" in build_options, + ) + ) class PyPyFinder(Finder): @@ -457,6 +504,16 @@ def render(downloads: list[PythonDownload]) -> None: return 2, int(prerelease[2:]) return 3, 0 + def variant_sort_key(variant: Variant | None) -> int: + if variant is None: + return 0 + match variant: + case Variant.FREETHREADED: + return 1 + case Variant.DEBUG: + return 2 + raise ValueError(f"Missing sort key implementation for variant: {variant}") + def sort_key(download: PythonDownload) -> tuple: # Sort by implementation, version (latest first), and then by triple. impl_order = [ImplementationName.CPYTHON, ImplementationName.PYPY] @@ -468,6 +525,7 @@ def render(downloads: list[PythonDownload]) -> None: -download.version.patch, -prerelease[0], -prerelease[1], + variant_sort_key(download.variant), download.triple, ) @@ -477,7 +535,7 @@ def render(downloads: list[PythonDownload]) -> None: for download in downloads: key = download.key() logging.info( - "Found %s%s", key, (" (%s)" % download.flavor) if download.flavor else "" + "Selected %s%s", key, (" (%s)" % download.flavor) if download.flavor else "" ) results[key] = { "name": download.implementation, @@ -490,6 +548,7 @@ def render(downloads: list[PythonDownload]) -> None: "prerelease": download.version.prerelease, "url": download.url, "sha256": download.sha256, + "variant": download.variant if download.variant else None, } VERSIONS_FILE.parent.mkdir(parents=True, exist_ok=True) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 2d5f8d4b7..b70ca71ed 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -10,8 +10,10 @@ use tracing::{debug, instrument, trace}; use which::{which, which_all}; use uv_cache::Cache; +use uv_fs::which::is_executable; use uv_fs::Simplified; use uv_pep440::{Prerelease, Version, VersionSpecifier, VersionSpecifiers}; +use uv_static::EnvVars; use uv_warnings::warn_user_once; use crate::downloads::PythonDownloadRequest; @@ -27,7 +29,6 @@ use crate::virtualenv::{ conda_prefix_from_env, virtualenv_from_env, virtualenv_from_working_dir, virtualenv_python_executable, }; -use crate::which::is_executable; use crate::{Interpreter, PythonVersion}; /// A request to find a Python installation. @@ -132,7 +133,7 @@ pub enum EnvironmentPreference { Any, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub enum PythonVariant { #[default] Default, @@ -198,8 +199,12 @@ pub enum Error { Io(#[from] io::Error), /// An error was encountering when retrieving interpreter information. - #[error(transparent)] - Query(#[from] crate::interpreter::Error), + #[error("Failed to inspect Python interpreter from {2} at `{}` ", .1.user_display())] + Query( + #[source] Box, + PathBuf, + PythonSource, + ), /// An error was encountered when interacting with a managed Python installation. #[error(transparent)] @@ -284,7 +289,7 @@ fn python_executables_from_environments<'a>( /// This function does not guarantee that the executables are valid Python interpreters. /// See [`python_interpreters_from_executables`]. fn python_executables_from_installed<'a>( - version: Option<&'a VersionRequest>, + version: &'a VersionRequest, implementation: Option<&'a ImplementationName>, preference: PythonPreference, ) -> Box> + 'a> { @@ -301,11 +306,7 @@ fn python_executables_from_installed<'a>( Ok(installations .into_iter() .filter(move |installation| { - if version.is_none() - || version.is_some_and(|version| { - version.matches_version(&installation.version()) - }) - { + if version.matches_version(&installation.version()) { true } else { debug!("Skipping incompatible managed installation `{installation}`"); @@ -324,23 +325,19 @@ fn python_executables_from_installed<'a>( }) .flatten(); - let from_windows = std::iter::once_with(move || { + let from_windows_registry = std::iter::once_with(move || { #[cfg(windows)] { // Skip interpreter probing if we already know the version doesn't match. let version_filter = move |entry: &WindowsPython| { - if let Some(version_request) = version { - if let Some(version) = &entry.version { - version_request.matches_version(version) - } else { - true - } + if let Some(found) = &entry.version { + version.matches_version(found) } else { true } }; - env::var_os("UV_TEST_PYTHON_PATH") + env::var_os(EnvVars::UV_TEST_PYTHON_PATH) .is_none() .then(|| { registry_pythons() @@ -372,14 +369,14 @@ fn python_executables_from_installed<'a>( PythonPreference::Managed => Box::new( from_managed_installations .chain(from_search_path) - .chain(from_windows), + .chain(from_windows_registry), ), PythonPreference::System => Box::new( from_search_path - .chain(from_windows) + .chain(from_windows_registry) .chain(from_managed_installations), ), - PythonPreference::OnlySystem => Box::new(from_search_path.chain(from_windows)), + PythonPreference::OnlySystem => Box::new(from_search_path.chain(from_windows_registry)), } } @@ -390,14 +387,14 @@ fn python_executables_from_installed<'a>( /// See [`python_executables_from_installed`] and [`python_executables_from_environments`] /// for more information on discovery. fn python_executables<'a>( - version: Option<&'a VersionRequest>, + version: &'a VersionRequest, implementation: Option<&'a ImplementationName>, environments: EnvironmentPreference, preference: PythonPreference, ) -> Box> + 'a> { // Always read from `UV_INTERNAL__PARENT_INTERPRETER` — it could be a system interpreter let from_parent_interpreter = std::iter::once_with(|| { - std::env::var_os("UV_INTERNAL__PARENT_INTERPRETER") + std::env::var_os(EnvVars::UV_INTERNAL__PARENT_INTERPRETER) .into_iter() .map(|path| Ok((PythonSource::ParentInterpreter, PathBuf::from(path)))) }) @@ -435,15 +432,14 @@ fn python_executables<'a>( /// If a `version` is not provided, we will only look for default executable names e.g. /// `python3` and `python` — `python3.9` and similar will not be included. fn python_executables_from_search_path<'a>( - version: Option<&'a VersionRequest>, + version: &'a VersionRequest, implementation: Option<&'a ImplementationName>, ) -> impl Iterator + 'a { // `UV_TEST_PYTHON_PATH` can be used to override `PATH` to limit Python executable availability in the test suite - let search_path = - env::var_os("UV_TEST_PYTHON_PATH").unwrap_or(env::var_os("PATH").unwrap_or_default()); + let search_path = env::var_os(EnvVars::UV_TEST_PYTHON_PATH) + .unwrap_or(env::var_os(EnvVars::PATH).unwrap_or_default()); - let version_request = version.unwrap_or(&VersionRequest::Default); - let possible_names: Vec<_> = version_request + let possible_names: Vec<_> = version .executable_names(implementation) .into_iter() .map(|name| name.to_string()) @@ -480,7 +476,7 @@ fn python_executables_from_search_path<'a>( // parameters, and the dir is local while we return the iterator. .collect::>() }) - .chain(find_all_minor(implementation, version_request, &dir_clone)) + .chain(find_all_minor(implementation, version, &dir_clone)) .filter(|path| !is_windows_store_shim(path)) .inspect(|path| trace!("Found possible Python executable: {}", path.display())) .chain( @@ -572,7 +568,7 @@ fn find_all_minor( /// /// See [`python_executables`] for more information on discovery. fn python_interpreters<'a>( - version: Option<&'a VersionRequest>, + version: &'a VersionRequest, implementation: Option<&'a ImplementationName>, environments: EnvironmentPreference, preference: PythonPreference, @@ -583,6 +579,7 @@ fn python_interpreters<'a>( cache, ) .filter(move |result| result_satisfies_environment_preference(result, environments)) + .filter(move |result| result_satisfies_version_request(result, version)) } /// Lazily convert Python executables into interpreters. @@ -600,7 +597,7 @@ fn python_interpreters_from_executables<'a>( path.display() ); }) - .map_err(Error::from) + .map_err(|err| Error::Query(Box::new(err), path, source)) .inspect_err(|err| debug!("{err}")), Err(err) => Err(err), }) @@ -656,6 +653,25 @@ fn satisfies_environment_preference( } } +/// Utility for applying [`VersionRequest::matches_interpreter`] to a result type. +fn result_satisfies_version_request( + result: &Result<(PythonSource, Interpreter), Error>, + request: &VersionRequest, +) -> bool { + result.as_ref().ok().map_or(true, |(source, interpreter)| { + let request = request.clone().into_request_for_source(*source); + if request.matches_interpreter(interpreter) { + true + } else { + debug!( + "Skipping interpreter at `{}` from {source}: does not satisfy request `{request}`", + interpreter.sys_executable().user_display() + ); + false + } + }) +} + /// Utility for applying [`satisfies_environment_preference`] to a result type. fn result_satisfies_environment_preference( result: &Result<(PythonSource, Interpreter), Error>, @@ -674,14 +690,17 @@ impl Error { match self { // When querying the Python interpreter fails, we will only raise errors that demonstrate that something is broken // If the Python interpreter returned a bad response, we'll continue searching for one that works - Error::Query(err) => match err { + Error::Query(err, _, source) => match &**err { InterpreterError::Encode(_) | InterpreterError::Io(_) | InterpreterError::SpawnFailed { .. } => true, InterpreterError::QueryScript { path, .. } | InterpreterError::UnexpectedResponse { path, .. } | InterpreterError::StatusCode { path, .. } => { - debug!("Skipping bad interpreter at {}: {err}", path.display()); + debug!( + "Skipping bad interpreter at {} from {source}: {err}", + path.display() + ); false } InterpreterError::NotFound(path) => { @@ -747,7 +766,11 @@ pub fn find_python_installations<'a>( environment_preference: environments, })) } - Err(err) => Err(err.into()), + Err(err) => Err(Error::Query( + Box::new(err), + path.clone(), + PythonSource::ProvidedPath, + )), } } else { Err(Error::SourceNotAllowed( @@ -770,7 +793,11 @@ pub fn find_python_installations<'a>( environment_preference: environments, })) } - Err(err) => Err(err.into()), + Err(err) => Err(Error::Query( + Box::new(err), + path.clone(), + PythonSource::ProvidedPath, + )), } } else { Err(Error::SourceNotAllowed( @@ -805,23 +832,18 @@ pub fn find_python_installations<'a>( } PythonRequest::Any => Box::new({ debug!("Searching for any Python interpreter in {preference}"); - python_interpreters( - Some(&VersionRequest::Any), - None, - environments, - preference, - cache, + python_interpreters(&VersionRequest::Any, None, environments, preference, cache).map( + |result| { + result + .map(PythonInstallation::from_tuple) + .map(FindPythonResult::Ok) + }, ) - .map(|result| { - result - .map(PythonInstallation::from_tuple) - .map(FindPythonResult::Ok) - }) }), PythonRequest::Default => Box::new({ debug!("Searching for default Python interpreter in {preference}"); python_interpreters( - Some(&VersionRequest::Default), + &VersionRequest::Default, None, environments, preference, @@ -839,32 +861,33 @@ pub fn find_python_installations<'a>( }; Box::new({ debug!("Searching for {request} in {preference}"); - python_interpreters(Some(version), None, environments, preference, cache) - .filter(|result| match result { - Err(_) => true, - Ok((_source, interpreter)) => version.matches_interpreter(interpreter), - }) - .map(|result| { - result - .map(PythonInstallation::from_tuple) - .map(FindPythonResult::Ok) - }) - }) - } - PythonRequest::Implementation(implementation) => Box::new({ - debug!("Searching for a {request} interpreter in {preference}"); - python_interpreters(None, Some(implementation), environments, preference, cache) - .filter(|result| match result { - Err(_) => true, - Ok((_source, interpreter)) => interpreter - .implementation_name() - .eq_ignore_ascii_case(implementation.into()), - }) - .map(|result| { + python_interpreters(version, None, environments, preference, cache).map(|result| { result .map(PythonInstallation::from_tuple) .map(FindPythonResult::Ok) }) + }) + } + PythonRequest::Implementation(implementation) => Box::new({ + debug!("Searching for a {request} interpreter in {preference}"); + python_interpreters( + &VersionRequest::Default, + Some(implementation), + environments, + preference, + cache, + ) + .filter(|result| match result { + Err(_) => true, + Ok((_source, interpreter)) => interpreter + .implementation_name() + .eq_ignore_ascii_case(implementation.into()), + }) + .map(|result| { + result + .map(PythonInstallation::from_tuple) + .map(FindPythonResult::Ok) + }) }), PythonRequest::ImplementationVersion(implementation, version) => { if let Err(err) = version.check_supported() { @@ -873,7 +896,7 @@ pub fn find_python_installations<'a>( Box::new({ debug!("Searching for {request} in {preference}"); python_interpreters( - Some(version), + version, Some(implementation), environments, preference, @@ -881,12 +904,9 @@ pub fn find_python_installations<'a>( ) .filter(|result| match result { Err(_) => true, - Ok((_source, interpreter)) => { - version.matches_interpreter(interpreter) - && interpreter - .implementation_name() - .eq_ignore_ascii_case(implementation.into()) - } + Ok((_source, interpreter)) => interpreter + .implementation_name() + .eq_ignore_ascii_case(implementation.into()), }) .map(|result| { result @@ -904,7 +924,7 @@ pub fn find_python_installations<'a>( Box::new({ debug!("Searching for {request} in {preference}"); python_interpreters( - request.version(), + request.version().unwrap_or(&VersionRequest::Default), request.implementation(), environments, preference, @@ -971,7 +991,7 @@ pub(crate) fn find_python_installation( // If it's an alternative implementation and alternative implementations aren't allowed, // skip it. Note we avoid querying these interpreters at all if they're on the search path - // and are not requested, but other sources such as the managed installations will include + // and are not requested, but other sources such as the managed installations can include // them. if installation.is_alternative_implementation() && !request.allows_alternative_implementations() @@ -1209,6 +1229,14 @@ fn is_windows_store_shim(_path: &Path) -> bool { false } +impl PythonVariant { + fn matches_interpreter(self, interpreter: &Interpreter) -> bool { + match self { + PythonVariant::Default => !interpreter.gil_disabled(), + PythonVariant::Freethreaded => interpreter.gil_disabled(), + } + } +} impl PythonRequest { /// Create a request from a string. /// @@ -1523,15 +1551,6 @@ impl PythonPreference { } } - /// Return the default [`PythonPreference`], respecting the `UV_TEST_PYTHON_PATH` variable. - pub fn default_from_env() -> Self { - if env::var_os("UV_TEST_PYTHON_PATH").is_some() { - Self::OnlySystem - } else { - Self::default() - } - } - pub(crate) fn allows_managed(self) -> bool { matches!(self, Self::Managed | Self::OnlyManaged) } @@ -1644,6 +1663,7 @@ impl std::fmt::Display for ExecutableName { } impl VersionRequest { + /// Return possible executable names for the given version request. pub(crate) fn executable_names( &self, implementation: Option<&ImplementationName>, @@ -1727,6 +1747,7 @@ impl VersionRequest { names } + /// Return the major version segment of the request, if any. pub(crate) fn major(&self) -> Option { match self { Self::Any | Self::Default | Self::Range(_, _) => None, @@ -1737,6 +1758,7 @@ impl VersionRequest { } } + /// Return the minor version segment of the request, if any. pub(crate) fn minor(&self) -> Option { match self { Self::Any | Self::Default | Self::Range(_, _) => None, @@ -1747,6 +1769,7 @@ impl VersionRequest { } } + /// Return the patch version segment of the request, if any. pub(crate) fn patch(&self) -> Option { match self { Self::Any | Self::Default | Self::Range(_, _) => None, @@ -1757,6 +1780,9 @@ impl VersionRequest { } } + /// Check if the request is for a version supported by uv. + /// + /// If not, an `Err` is returned with an explanatory message. pub(crate) fn check_supported(&self) -> Result<(), String> { match self { Self::Any | Self::Default => (), @@ -1805,29 +1831,55 @@ impl VersionRequest { Ok(()) } - /// Check if a interpreter matches the requested Python version. - pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool { - if self.is_freethreaded() && !interpreter.gil_disabled() { - return false; - } + /// Change this request into a request appropriate for the given [`PythonSource`]. + /// + /// For example, if [`VersionRequest::Default`] is requested, it will be changed to + /// [`VersionRequest::Any`] for sources that should allow non-default interpreters like + /// free-threaded variants. + #[must_use] + pub(crate) fn into_request_for_source(self, source: PythonSource) -> Self { match self { - Self::Any | Self::Default => true, - Self::Major(major, _) => interpreter.python_major() == *major, - Self::MajorMinor(major, minor, _) => { - (interpreter.python_major(), interpreter.python_minor()) == (*major, *minor) + Self::Default => match source { + PythonSource::ParentInterpreter + | PythonSource::CondaPrefix + | PythonSource::ProvidedPath + | PythonSource::DiscoveredEnvironment + | PythonSource::ActiveEnvironment => Self::Any, + PythonSource::SearchPath + | PythonSource::Registry + | PythonSource::MicrosoftStore + | PythonSource::Managed => Self::Default, + }, + _ => self, + } + } + + /// Check if a interpreter matches the request. + pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool { + match self { + Self::Any => true, + // Do not use free-threaded interpreters by default + Self::Default => PythonVariant::Default.matches_interpreter(interpreter), + Self::Major(major, variant) => { + interpreter.python_major() == *major && variant.matches_interpreter(interpreter) } - Self::MajorMinorPatch(major, minor, patch, _) => { + Self::MajorMinor(major, minor, variant) => { + (interpreter.python_major(), interpreter.python_minor()) == (*major, *minor) + && variant.matches_interpreter(interpreter) + } + Self::MajorMinorPatch(major, minor, patch, variant) => { ( interpreter.python_major(), interpreter.python_minor(), interpreter.python_patch(), ) == (*major, *minor, *patch) + && variant.matches_interpreter(interpreter) } - Self::Range(specifiers, _) => { + Self::Range(specifiers, variant) => { let version = interpreter.python_version().only_release(); - specifiers.contains(&version) + specifiers.contains(&version) && variant.matches_interpreter(interpreter) } - Self::MajorMinorPrerelease(major, minor, prerelease, _) => { + Self::MajorMinorPrerelease(major, minor, prerelease, variant) => { let version = interpreter.python_version(); let Some(interpreter_prerelease) = version.pre() else { return false; @@ -1837,10 +1889,15 @@ impl VersionRequest { interpreter.python_minor(), interpreter_prerelease, ) == (*major, *minor, *prerelease) + && variant.matches_interpreter(interpreter) } } } + /// Check if a version is compatible with the request. + /// + /// WARNING: Use [`VersionRequest::matches_interpreter`] too. This method is only suitable to + /// avoid querying interpreters if it's clear it cannot fulfull the request. pub(crate) fn matches_version(&self, version: &PythonVersion) -> bool { match self { Self::Any | Self::Default => true, @@ -1860,6 +1917,10 @@ impl VersionRequest { } } + /// Check if major and minor version segments are compatible with the request. + /// + /// WARNING: Use [`VersionRequest::matches_interpreter`] too. This method is only suitable to + /// avoid querying interpreters if it's clear it cannot fulfull the request. fn matches_major_minor(&self, major: u8, minor: u8) -> bool { match self { Self::Any | Self::Default => true, @@ -1879,7 +1940,18 @@ impl VersionRequest { } } - pub(crate) fn matches_major_minor_patch(&self, major: u8, minor: u8, patch: u8) -> bool { + /// Check if major, minor, patch, and prerelease version segments are compatible with the + /// request. + /// + /// WARNING: Use [`VersionRequest::matches_interpreter`] too. This method is only suitable to + /// avoid querying interpreters if it's clear it cannot fulfull the request. + pub(crate) fn matches_major_minor_patch_prerelease( + &self, + major: u8, + minor: u8, + patch: u8, + prerelease: Option, + ) -> bool { match self { Self::Any | Self::Default => true, Self::Major(self_major, _) => *self_major == major, @@ -1889,19 +1961,19 @@ impl VersionRequest { Self::MajorMinorPatch(self_major, self_minor, self_patch, _) => { (*self_major, *self_minor, *self_patch) == (major, minor, patch) } - Self::Range(specifiers, _) => specifiers.contains(&Version::new([ - u64::from(major), - u64::from(minor), - u64::from(patch), - ])), - Self::MajorMinorPrerelease(self_major, self_minor, _, _) => { + Self::Range(specifiers, _) => specifiers.contains( + &Version::new([u64::from(major), u64::from(minor), u64::from(patch)]) + .with_pre(prerelease), + ), + Self::MajorMinorPrerelease(self_major, self_minor, self_prerelease, _) => { // Pre-releases of Python versions are always for the zero patch version (*self_major, *self_minor, 0) == (major, minor, patch) + && prerelease.map_or(true, |pre| *self_prerelease == pre) } } } - /// Return true if a patch version is present in the request. + /// Whether a patch version segment is present in the request. fn has_patch(&self) -> bool { match self { Self::Any | Self::Default => false, @@ -1915,7 +1987,7 @@ impl VersionRequest { /// Return a new [`VersionRequest`] without the patch version if possible. /// - /// If the patch version is not present, it is returned unchanged. + /// If the patch version is not present, the request is returned unchanged. #[must_use] fn without_patch(self) -> Self { match self { @@ -1946,6 +2018,7 @@ impl VersionRequest { } } + /// Whether this request is for a free-threaded Python variant. pub(crate) fn is_freethreaded(&self) -> bool { match self { Self::Any | Self::Default => false, @@ -1956,6 +2029,42 @@ impl VersionRequest { | Self::Range(_, variant) => variant == &PythonVariant::Freethreaded, } } + + /// Return a new [`VersionRequest`] with the [`PythonVariant`] if it has one. + /// + /// This is useful for converting the string representation to pep440. + #[must_use] + pub fn without_python_variant(self) -> Self { + // TODO(zanieb): Replace this entire function with a utility that casts this to a version + // without using `VersionRequest::to_string`. + match self { + Self::Any | Self::Default => self, + Self::Major(major, _) => Self::Major(major, PythonVariant::Default), + Self::MajorMinor(major, minor, _) => { + Self::MajorMinor(major, minor, PythonVariant::Default) + } + Self::MajorMinorPatch(major, minor, patch, _) => { + Self::MajorMinorPatch(major, minor, patch, PythonVariant::Default) + } + Self::MajorMinorPrerelease(major, minor, prerelease, _) => { + Self::MajorMinorPrerelease(major, minor, prerelease, PythonVariant::Default) + } + Self::Range(specifiers, _) => Self::Range(specifiers, PythonVariant::Default), + } + } + + /// Return the [`PythonVariant`] of the request, if any. + pub(crate) fn variant(&self) -> Option { + match self { + Self::Any => None, + Self::Default => Some(PythonVariant::Default), + Self::Major(_, variant) + | Self::MajorMinor(_, _, variant) + | Self::MajorMinorPatch(_, _, _, variant) + | Self::MajorMinorPrerelease(_, _, _, variant) + | Self::Range(_, variant) => Some(*variant), + } + } } impl FromStr for VersionRequest { @@ -2030,6 +2139,27 @@ impl FromStr for VersionRequest { } } +impl FromStr for PythonVariant { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "t" | "freethreaded" => Ok(Self::Freethreaded), + "" => Ok(Self::Default), + _ => Err(()), + } + } +} + +impl std::fmt::Display for PythonVariant { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Default => f.write_str("default"), + Self::Freethreaded => f.write_str("freethreaded"), + } + } +} + fn parse_version_specifiers_request( s: &str, variant: PythonVariant, @@ -2226,534 +2356,4 @@ fn split_wheel_tag_release_version(version: Version) -> Version { } #[cfg(test)] -mod tests { - use std::{path::PathBuf, str::FromStr}; - - use assert_fs::{prelude::*, TempDir}; - use test_log::test; - use uv_pep440::{Prerelease, PrereleaseKind, VersionSpecifiers}; - - use crate::{ - discovery::{PythonRequest, VersionRequest}, - implementation::ImplementationName, - }; - - use super::{Error, PythonVariant}; - - #[test] - fn interpreter_request_from_str() { - assert_eq!(PythonRequest::parse("any"), PythonRequest::Any); - assert_eq!(PythonRequest::parse("default"), PythonRequest::Default); - assert_eq!( - PythonRequest::parse("3.12"), - PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()) - ); - assert_eq!( - PythonRequest::parse(">=3.12"), - PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap()) - ); - assert_eq!( - PythonRequest::parse(">=3.12,<3.13"), - PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap()) - ); - assert_eq!( - PythonRequest::parse(">=3.12,<3.13"), - PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap()) - ); - - assert_eq!( - PythonRequest::parse("3.13.0a1"), - PythonRequest::Version(VersionRequest::from_str("3.13.0a1").unwrap()) - ); - assert_eq!( - PythonRequest::parse("3.13.0b5"), - PythonRequest::Version(VersionRequest::from_str("3.13.0b5").unwrap()) - ); - assert_eq!( - PythonRequest::parse("3.13.0rc1"), - PythonRequest::Version(VersionRequest::from_str("3.13.0rc1").unwrap()) - ); - assert_eq!( - PythonRequest::parse("3.13.1rc1"), - PythonRequest::ExecutableName("3.13.1rc1".to_string()), - "Pre-release version requests require a patch version of zero" - ); - assert_eq!( - PythonRequest::parse("3rc1"), - PythonRequest::ExecutableName("3rc1".to_string()), - "Pre-release version requests require a minor version" - ); - - assert_eq!( - PythonRequest::parse("cpython"), - PythonRequest::Implementation(ImplementationName::CPython) - ); - assert_eq!( - PythonRequest::parse("cpython3.12.2"), - PythonRequest::ImplementationVersion( - ImplementationName::CPython, - VersionRequest::from_str("3.12.2").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("pypy"), - PythonRequest::Implementation(ImplementationName::PyPy) - ); - assert_eq!( - PythonRequest::parse("pp"), - PythonRequest::Implementation(ImplementationName::PyPy) - ); - assert_eq!( - PythonRequest::parse("graalpy"), - PythonRequest::Implementation(ImplementationName::GraalPy) - ); - assert_eq!( - PythonRequest::parse("gp"), - PythonRequest::Implementation(ImplementationName::GraalPy) - ); - assert_eq!( - PythonRequest::parse("cp"), - PythonRequest::Implementation(ImplementationName::CPython) - ); - assert_eq!( - PythonRequest::parse("pypy3.10"), - PythonRequest::ImplementationVersion( - ImplementationName::PyPy, - VersionRequest::from_str("3.10").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("pp310"), - PythonRequest::ImplementationVersion( - ImplementationName::PyPy, - VersionRequest::from_str("3.10").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("graalpy3.10"), - PythonRequest::ImplementationVersion( - ImplementationName::GraalPy, - VersionRequest::from_str("3.10").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("gp310"), - PythonRequest::ImplementationVersion( - ImplementationName::GraalPy, - VersionRequest::from_str("3.10").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("cp38"), - PythonRequest::ImplementationVersion( - ImplementationName::CPython, - VersionRequest::from_str("3.8").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("pypy@3.10"), - PythonRequest::ImplementationVersion( - ImplementationName::PyPy, - VersionRequest::from_str("3.10").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("pypy310"), - PythonRequest::ImplementationVersion( - ImplementationName::PyPy, - VersionRequest::from_str("3.10").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("graalpy@3.10"), - PythonRequest::ImplementationVersion( - ImplementationName::GraalPy, - VersionRequest::from_str("3.10").unwrap(), - ) - ); - assert_eq!( - PythonRequest::parse("graalpy310"), - PythonRequest::ImplementationVersion( - ImplementationName::GraalPy, - VersionRequest::from_str("3.10").unwrap(), - ) - ); - - let tempdir = TempDir::new().unwrap(); - assert_eq!( - PythonRequest::parse(tempdir.path().to_str().unwrap()), - PythonRequest::Directory(tempdir.path().to_path_buf()), - "An existing directory is treated as a directory" - ); - assert_eq!( - PythonRequest::parse(tempdir.child("foo").path().to_str().unwrap()), - PythonRequest::File(tempdir.child("foo").path().to_path_buf()), - "A path that does not exist is treated as a file" - ); - tempdir.child("bar").touch().unwrap(); - assert_eq!( - PythonRequest::parse(tempdir.child("bar").path().to_str().unwrap()), - PythonRequest::File(tempdir.child("bar").path().to_path_buf()), - "An existing file is treated as a file" - ); - assert_eq!( - PythonRequest::parse("./foo"), - PythonRequest::File(PathBuf::from_str("./foo").unwrap()), - "A string with a file system separator is treated as a file" - ); - assert_eq!( - PythonRequest::parse("3.13t"), - PythonRequest::Version(VersionRequest::from_str("3.13t").unwrap()) - ); - } - - #[test] - fn interpreter_request_to_canonical_string() { - assert_eq!(PythonRequest::Default.to_canonical_string(), "default"); - assert_eq!(PythonRequest::Any.to_canonical_string(), "any"); - assert_eq!( - PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()).to_canonical_string(), - "3.12" - ); - assert_eq!( - PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap()) - .to_canonical_string(), - ">=3.12" - ); - assert_eq!( - PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap()) - .to_canonical_string(), - ">=3.12, <3.13" - ); - - assert_eq!( - PythonRequest::Version(VersionRequest::from_str("3.13.0a1").unwrap()) - .to_canonical_string(), - "3.13a1" - ); - - assert_eq!( - PythonRequest::Version(VersionRequest::from_str("3.13.0b5").unwrap()) - .to_canonical_string(), - "3.13b5" - ); - - assert_eq!( - PythonRequest::Version(VersionRequest::from_str("3.13.0rc1").unwrap()) - .to_canonical_string(), - "3.13rc1" - ); - - assert_eq!( - PythonRequest::Version(VersionRequest::from_str("313rc4").unwrap()) - .to_canonical_string(), - "3.13rc4" - ); - - assert_eq!( - PythonRequest::ExecutableName("foo".to_string()).to_canonical_string(), - "foo" - ); - assert_eq!( - PythonRequest::Implementation(ImplementationName::CPython).to_canonical_string(), - "cpython" - ); - assert_eq!( - PythonRequest::ImplementationVersion( - ImplementationName::CPython, - VersionRequest::from_str("3.12.2").unwrap(), - ) - .to_canonical_string(), - "cpython@3.12.2" - ); - assert_eq!( - PythonRequest::Implementation(ImplementationName::PyPy).to_canonical_string(), - "pypy" - ); - assert_eq!( - PythonRequest::ImplementationVersion( - ImplementationName::PyPy, - VersionRequest::from_str("3.10").unwrap(), - ) - .to_canonical_string(), - "pypy@3.10" - ); - assert_eq!( - PythonRequest::Implementation(ImplementationName::GraalPy).to_canonical_string(), - "graalpy" - ); - assert_eq!( - PythonRequest::ImplementationVersion( - ImplementationName::GraalPy, - VersionRequest::from_str("3.10").unwrap(), - ) - .to_canonical_string(), - "graalpy@3.10" - ); - - let tempdir = TempDir::new().unwrap(); - assert_eq!( - PythonRequest::Directory(tempdir.path().to_path_buf()).to_canonical_string(), - tempdir.path().to_str().unwrap(), - "An existing directory is treated as a directory" - ); - assert_eq!( - PythonRequest::File(tempdir.child("foo").path().to_path_buf()).to_canonical_string(), - tempdir.child("foo").path().to_str().unwrap(), - "A path that does not exist is treated as a file" - ); - tempdir.child("bar").touch().unwrap(); - assert_eq!( - PythonRequest::File(tempdir.child("bar").path().to_path_buf()).to_canonical_string(), - tempdir.child("bar").path().to_str().unwrap(), - "An existing file is treated as a file" - ); - assert_eq!( - PythonRequest::File(PathBuf::from_str("./foo").unwrap()).to_canonical_string(), - "./foo", - "A string with a file system separator is treated as a file" - ); - } - - #[test] - fn version_request_from_str() { - assert_eq!( - VersionRequest::from_str("3").unwrap(), - VersionRequest::Major(3, PythonVariant::Default) - ); - assert_eq!( - VersionRequest::from_str("3.12").unwrap(), - VersionRequest::MajorMinor(3, 12, PythonVariant::Default) - ); - assert_eq!( - VersionRequest::from_str("3.12.1").unwrap(), - VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default) - ); - assert!(VersionRequest::from_str("1.foo.1").is_err()); - assert_eq!( - VersionRequest::from_str("3").unwrap(), - VersionRequest::Major(3, PythonVariant::Default) - ); - assert_eq!( - VersionRequest::from_str("38").unwrap(), - VersionRequest::MajorMinor(3, 8, PythonVariant::Default) - ); - assert_eq!( - VersionRequest::from_str("312").unwrap(), - VersionRequest::MajorMinor(3, 12, PythonVariant::Default) - ); - assert_eq!( - VersionRequest::from_str("3100").unwrap(), - VersionRequest::MajorMinor(3, 100, PythonVariant::Default) - ); - assert_eq!( - VersionRequest::from_str("3.13a1").unwrap(), - VersionRequest::MajorMinorPrerelease( - 3, - 13, - Prerelease { - kind: PrereleaseKind::Alpha, - number: 1 - }, - PythonVariant::Default - ) - ); - assert_eq!( - VersionRequest::from_str("313b1").unwrap(), - VersionRequest::MajorMinorPrerelease( - 3, - 13, - Prerelease { - kind: PrereleaseKind::Beta, - number: 1 - }, - PythonVariant::Default - ) - ); - assert_eq!( - VersionRequest::from_str("3.13.0b2").unwrap(), - VersionRequest::MajorMinorPrerelease( - 3, - 13, - Prerelease { - kind: PrereleaseKind::Beta, - number: 2 - }, - PythonVariant::Default - ) - ); - assert_eq!( - VersionRequest::from_str("3.13.0rc3").unwrap(), - VersionRequest::MajorMinorPrerelease( - 3, - 13, - Prerelease { - kind: PrereleaseKind::Rc, - number: 3 - }, - PythonVariant::Default - ) - ); - assert!( - matches!( - VersionRequest::from_str("3rc1"), - Err(Error::InvalidVersionRequest(_)) - ), - "Pre-release version requests require a minor version" - ); - assert!( - matches!( - VersionRequest::from_str("3.13.2rc1"), - Err(Error::InvalidVersionRequest(_)) - ), - "Pre-release version requests require a patch version of zero" - ); - assert!( - matches!( - VersionRequest::from_str("3.12-dev"), - Err(Error::InvalidVersionRequest(_)) - ), - "Development version segments are not allowed" - ); - assert!( - matches!( - VersionRequest::from_str("3.12+local"), - Err(Error::InvalidVersionRequest(_)) - ), - "Local version segments are not allowed" - ); - assert!( - matches!( - VersionRequest::from_str("3.12.post0"), - Err(Error::InvalidVersionRequest(_)) - ), - "Post version segments are not allowed" - ); - assert!( - // Test for overflow - matches!( - VersionRequest::from_str("31000"), - Err(Error::InvalidVersionRequest(_)) - ) - ); - assert_eq!( - VersionRequest::from_str("3t").unwrap(), - VersionRequest::Major(3, PythonVariant::Freethreaded) - ); - assert_eq!( - VersionRequest::from_str("313t").unwrap(), - VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded) - ); - assert_eq!( - VersionRequest::from_str("3.13t").unwrap(), - VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded) - ); - assert_eq!( - VersionRequest::from_str(">=3.13t").unwrap(), - VersionRequest::Range( - VersionSpecifiers::from_str(">=3.13").unwrap(), - PythonVariant::Freethreaded - ) - ); - assert_eq!( - VersionRequest::from_str(">=3.13").unwrap(), - VersionRequest::Range( - VersionSpecifiers::from_str(">=3.13").unwrap(), - PythonVariant::Default - ) - ); - assert_eq!( - VersionRequest::from_str(">=3.12,<3.14t").unwrap(), - VersionRequest::Range( - VersionSpecifiers::from_str(">=3.12,<3.14").unwrap(), - PythonVariant::Freethreaded - ) - ); - assert!(matches!( - VersionRequest::from_str("3.13tt"), - Err(Error::InvalidVersionRequest(_)) - )); - } - - #[test] - fn executable_names_from_request() { - fn case(request: &str, expected: &[&str]) { - let (implementation, version) = match PythonRequest::parse(request) { - PythonRequest::Any => (None, VersionRequest::Any), - PythonRequest::Default => (None, VersionRequest::Default), - PythonRequest::Version(version) => (None, version), - PythonRequest::ImplementationVersion(implementation, version) => { - (Some(implementation), version) - } - PythonRequest::Implementation(implementation) => { - (Some(implementation), VersionRequest::Default) - } - result => { - panic!("Test cases should request versions or implementations; got {result:?}") - } - }; - - let result: Vec<_> = version - .executable_names(implementation.as_ref()) - .into_iter() - .map(|name| name.to_string()) - .collect(); - - let expected: Vec<_> = expected - .iter() - .map(|name| format!("{name}{exe}", exe = std::env::consts::EXE_SUFFIX)) - .collect(); - - assert_eq!(result, expected, "mismatch for case \"{request}\""); - } - - case( - "any", - &[ - "python", "python3", "cpython", "pypy", "graalpy", "cpython3", "pypy3", "graalpy3", - ], - ); - - case("default", &["python", "python3"]); - - case("3", &["python", "python3"]); - - case("4", &["python", "python4"]); - - case("3.13", &["python", "python3", "python3.13"]); - - case( - "pypy@3.10", - &[ - "python", - "python3", - "python3.10", - "pypy", - "pypy3", - "pypy3.10", - ], - ); - - case( - "3.13t", - &[ - "python", - "python3", - "python3.13", - "pythont", - "python3t", - "python3.13t", - ], - ); - - case( - "3.13.2", - &["python", "python3", "python3.13", "python3.13.2"], - ); - - case( - "3.13rc2", - &["python", "python3", "python3.13", "python3.13rc2"], - ); - } -} +mod tests; diff --git a/crates/uv-python/src/discovery/tests.rs b/crates/uv-python/src/discovery/tests.rs new file mode 100644 index 000000000..9dbd688f3 --- /dev/null +++ b/crates/uv-python/src/discovery/tests.rs @@ -0,0 +1,525 @@ +use std::{path::PathBuf, str::FromStr}; + +use assert_fs::{prelude::*, TempDir}; +use test_log::test; +use uv_pep440::{Prerelease, PrereleaseKind, VersionSpecifiers}; + +use crate::{ + discovery::{PythonRequest, VersionRequest}, + implementation::ImplementationName, +}; + +use super::{Error, PythonVariant}; + +#[test] +fn interpreter_request_from_str() { + assert_eq!(PythonRequest::parse("any"), PythonRequest::Any); + assert_eq!(PythonRequest::parse("default"), PythonRequest::Default); + assert_eq!( + PythonRequest::parse("3.12"), + PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()) + ); + assert_eq!( + PythonRequest::parse(">=3.12"), + PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap()) + ); + assert_eq!( + PythonRequest::parse(">=3.12,<3.13"), + PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap()) + ); + assert_eq!( + PythonRequest::parse(">=3.12,<3.13"), + PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap()) + ); + + assert_eq!( + PythonRequest::parse("3.13.0a1"), + PythonRequest::Version(VersionRequest::from_str("3.13.0a1").unwrap()) + ); + assert_eq!( + PythonRequest::parse("3.13.0b5"), + PythonRequest::Version(VersionRequest::from_str("3.13.0b5").unwrap()) + ); + assert_eq!( + PythonRequest::parse("3.13.0rc1"), + PythonRequest::Version(VersionRequest::from_str("3.13.0rc1").unwrap()) + ); + assert_eq!( + PythonRequest::parse("3.13.1rc1"), + PythonRequest::ExecutableName("3.13.1rc1".to_string()), + "Pre-release version requests require a patch version of zero" + ); + assert_eq!( + PythonRequest::parse("3rc1"), + PythonRequest::ExecutableName("3rc1".to_string()), + "Pre-release version requests require a minor version" + ); + + assert_eq!( + PythonRequest::parse("cpython"), + PythonRequest::Implementation(ImplementationName::CPython) + ); + assert_eq!( + PythonRequest::parse("cpython3.12.2"), + PythonRequest::ImplementationVersion( + ImplementationName::CPython, + VersionRequest::from_str("3.12.2").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("pypy"), + PythonRequest::Implementation(ImplementationName::PyPy) + ); + assert_eq!( + PythonRequest::parse("pp"), + PythonRequest::Implementation(ImplementationName::PyPy) + ); + assert_eq!( + PythonRequest::parse("graalpy"), + PythonRequest::Implementation(ImplementationName::GraalPy) + ); + assert_eq!( + PythonRequest::parse("gp"), + PythonRequest::Implementation(ImplementationName::GraalPy) + ); + assert_eq!( + PythonRequest::parse("cp"), + PythonRequest::Implementation(ImplementationName::CPython) + ); + assert_eq!( + PythonRequest::parse("pypy3.10"), + PythonRequest::ImplementationVersion( + ImplementationName::PyPy, + VersionRequest::from_str("3.10").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("pp310"), + PythonRequest::ImplementationVersion( + ImplementationName::PyPy, + VersionRequest::from_str("3.10").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("graalpy3.10"), + PythonRequest::ImplementationVersion( + ImplementationName::GraalPy, + VersionRequest::from_str("3.10").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("gp310"), + PythonRequest::ImplementationVersion( + ImplementationName::GraalPy, + VersionRequest::from_str("3.10").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("cp38"), + PythonRequest::ImplementationVersion( + ImplementationName::CPython, + VersionRequest::from_str("3.8").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("pypy@3.10"), + PythonRequest::ImplementationVersion( + ImplementationName::PyPy, + VersionRequest::from_str("3.10").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("pypy310"), + PythonRequest::ImplementationVersion( + ImplementationName::PyPy, + VersionRequest::from_str("3.10").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("graalpy@3.10"), + PythonRequest::ImplementationVersion( + ImplementationName::GraalPy, + VersionRequest::from_str("3.10").unwrap(), + ) + ); + assert_eq!( + PythonRequest::parse("graalpy310"), + PythonRequest::ImplementationVersion( + ImplementationName::GraalPy, + VersionRequest::from_str("3.10").unwrap(), + ) + ); + + let tempdir = TempDir::new().unwrap(); + assert_eq!( + PythonRequest::parse(tempdir.path().to_str().unwrap()), + PythonRequest::Directory(tempdir.path().to_path_buf()), + "An existing directory is treated as a directory" + ); + assert_eq!( + PythonRequest::parse(tempdir.child("foo").path().to_str().unwrap()), + PythonRequest::File(tempdir.child("foo").path().to_path_buf()), + "A path that does not exist is treated as a file" + ); + tempdir.child("bar").touch().unwrap(); + assert_eq!( + PythonRequest::parse(tempdir.child("bar").path().to_str().unwrap()), + PythonRequest::File(tempdir.child("bar").path().to_path_buf()), + "An existing file is treated as a file" + ); + assert_eq!( + PythonRequest::parse("./foo"), + PythonRequest::File(PathBuf::from_str("./foo").unwrap()), + "A string with a file system separator is treated as a file" + ); + assert_eq!( + PythonRequest::parse("3.13t"), + PythonRequest::Version(VersionRequest::from_str("3.13t").unwrap()) + ); +} + +#[test] +fn interpreter_request_to_canonical_string() { + assert_eq!(PythonRequest::Default.to_canonical_string(), "default"); + assert_eq!(PythonRequest::Any.to_canonical_string(), "any"); + assert_eq!( + PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()).to_canonical_string(), + "3.12" + ); + assert_eq!( + PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap()).to_canonical_string(), + ">=3.12" + ); + assert_eq!( + PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap()) + .to_canonical_string(), + ">=3.12, <3.13" + ); + + assert_eq!( + PythonRequest::Version(VersionRequest::from_str("3.13.0a1").unwrap()).to_canonical_string(), + "3.13a1" + ); + + assert_eq!( + PythonRequest::Version(VersionRequest::from_str("3.13.0b5").unwrap()).to_canonical_string(), + "3.13b5" + ); + + assert_eq!( + PythonRequest::Version(VersionRequest::from_str("3.13.0rc1").unwrap()) + .to_canonical_string(), + "3.13rc1" + ); + + assert_eq!( + PythonRequest::Version(VersionRequest::from_str("313rc4").unwrap()).to_canonical_string(), + "3.13rc4" + ); + + assert_eq!( + PythonRequest::ExecutableName("foo".to_string()).to_canonical_string(), + "foo" + ); + assert_eq!( + PythonRequest::Implementation(ImplementationName::CPython).to_canonical_string(), + "cpython" + ); + assert_eq!( + PythonRequest::ImplementationVersion( + ImplementationName::CPython, + VersionRequest::from_str("3.12.2").unwrap(), + ) + .to_canonical_string(), + "cpython@3.12.2" + ); + assert_eq!( + PythonRequest::Implementation(ImplementationName::PyPy).to_canonical_string(), + "pypy" + ); + assert_eq!( + PythonRequest::ImplementationVersion( + ImplementationName::PyPy, + VersionRequest::from_str("3.10").unwrap(), + ) + .to_canonical_string(), + "pypy@3.10" + ); + assert_eq!( + PythonRequest::Implementation(ImplementationName::GraalPy).to_canonical_string(), + "graalpy" + ); + assert_eq!( + PythonRequest::ImplementationVersion( + ImplementationName::GraalPy, + VersionRequest::from_str("3.10").unwrap(), + ) + .to_canonical_string(), + "graalpy@3.10" + ); + + let tempdir = TempDir::new().unwrap(); + assert_eq!( + PythonRequest::Directory(tempdir.path().to_path_buf()).to_canonical_string(), + tempdir.path().to_str().unwrap(), + "An existing directory is treated as a directory" + ); + assert_eq!( + PythonRequest::File(tempdir.child("foo").path().to_path_buf()).to_canonical_string(), + tempdir.child("foo").path().to_str().unwrap(), + "A path that does not exist is treated as a file" + ); + tempdir.child("bar").touch().unwrap(); + assert_eq!( + PythonRequest::File(tempdir.child("bar").path().to_path_buf()).to_canonical_string(), + tempdir.child("bar").path().to_str().unwrap(), + "An existing file is treated as a file" + ); + assert_eq!( + PythonRequest::File(PathBuf::from_str("./foo").unwrap()).to_canonical_string(), + "./foo", + "A string with a file system separator is treated as a file" + ); +} + +#[test] +fn version_request_from_str() { + assert_eq!( + VersionRequest::from_str("3").unwrap(), + VersionRequest::Major(3, PythonVariant::Default) + ); + assert_eq!( + VersionRequest::from_str("3.12").unwrap(), + VersionRequest::MajorMinor(3, 12, PythonVariant::Default) + ); + assert_eq!( + VersionRequest::from_str("3.12.1").unwrap(), + VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default) + ); + assert!(VersionRequest::from_str("1.foo.1").is_err()); + assert_eq!( + VersionRequest::from_str("3").unwrap(), + VersionRequest::Major(3, PythonVariant::Default) + ); + assert_eq!( + VersionRequest::from_str("38").unwrap(), + VersionRequest::MajorMinor(3, 8, PythonVariant::Default) + ); + assert_eq!( + VersionRequest::from_str("312").unwrap(), + VersionRequest::MajorMinor(3, 12, PythonVariant::Default) + ); + assert_eq!( + VersionRequest::from_str("3100").unwrap(), + VersionRequest::MajorMinor(3, 100, PythonVariant::Default) + ); + assert_eq!( + VersionRequest::from_str("3.13a1").unwrap(), + VersionRequest::MajorMinorPrerelease( + 3, + 13, + Prerelease { + kind: PrereleaseKind::Alpha, + number: 1 + }, + PythonVariant::Default + ) + ); + assert_eq!( + VersionRequest::from_str("313b1").unwrap(), + VersionRequest::MajorMinorPrerelease( + 3, + 13, + Prerelease { + kind: PrereleaseKind::Beta, + number: 1 + }, + PythonVariant::Default + ) + ); + assert_eq!( + VersionRequest::from_str("3.13.0b2").unwrap(), + VersionRequest::MajorMinorPrerelease( + 3, + 13, + Prerelease { + kind: PrereleaseKind::Beta, + number: 2 + }, + PythonVariant::Default + ) + ); + assert_eq!( + VersionRequest::from_str("3.13.0rc3").unwrap(), + VersionRequest::MajorMinorPrerelease( + 3, + 13, + Prerelease { + kind: PrereleaseKind::Rc, + number: 3 + }, + PythonVariant::Default + ) + ); + assert!( + matches!( + VersionRequest::from_str("3rc1"), + Err(Error::InvalidVersionRequest(_)) + ), + "Pre-release version requests require a minor version" + ); + assert!( + matches!( + VersionRequest::from_str("3.13.2rc1"), + Err(Error::InvalidVersionRequest(_)) + ), + "Pre-release version requests require a patch version of zero" + ); + assert!( + matches!( + VersionRequest::from_str("3.12-dev"), + Err(Error::InvalidVersionRequest(_)) + ), + "Development version segments are not allowed" + ); + assert!( + matches!( + VersionRequest::from_str("3.12+local"), + Err(Error::InvalidVersionRequest(_)) + ), + "Local version segments are not allowed" + ); + assert!( + matches!( + VersionRequest::from_str("3.12.post0"), + Err(Error::InvalidVersionRequest(_)) + ), + "Post version segments are not allowed" + ); + assert!( + // Test for overflow + matches!( + VersionRequest::from_str("31000"), + Err(Error::InvalidVersionRequest(_)) + ) + ); + assert_eq!( + VersionRequest::from_str("3t").unwrap(), + VersionRequest::Major(3, PythonVariant::Freethreaded) + ); + assert_eq!( + VersionRequest::from_str("313t").unwrap(), + VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded) + ); + assert_eq!( + VersionRequest::from_str("3.13t").unwrap(), + VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded) + ); + assert_eq!( + VersionRequest::from_str(">=3.13t").unwrap(), + VersionRequest::Range( + VersionSpecifiers::from_str(">=3.13").unwrap(), + PythonVariant::Freethreaded + ) + ); + assert_eq!( + VersionRequest::from_str(">=3.13").unwrap(), + VersionRequest::Range( + VersionSpecifiers::from_str(">=3.13").unwrap(), + PythonVariant::Default + ) + ); + assert_eq!( + VersionRequest::from_str(">=3.12,<3.14t").unwrap(), + VersionRequest::Range( + VersionSpecifiers::from_str(">=3.12,<3.14").unwrap(), + PythonVariant::Freethreaded + ) + ); + assert!(matches!( + VersionRequest::from_str("3.13tt"), + Err(Error::InvalidVersionRequest(_)) + )); +} + +#[test] +fn executable_names_from_request() { + fn case(request: &str, expected: &[&str]) { + let (implementation, version) = match PythonRequest::parse(request) { + PythonRequest::Any => (None, VersionRequest::Any), + PythonRequest::Default => (None, VersionRequest::Default), + PythonRequest::Version(version) => (None, version), + PythonRequest::ImplementationVersion(implementation, version) => { + (Some(implementation), version) + } + PythonRequest::Implementation(implementation) => { + (Some(implementation), VersionRequest::Default) + } + result => { + panic!("Test cases should request versions or implementations; got {result:?}") + } + }; + + let result: Vec<_> = version + .executable_names(implementation.as_ref()) + .into_iter() + .map(|name| name.to_string()) + .collect(); + + let expected: Vec<_> = expected + .iter() + .map(|name| format!("{name}{exe}", exe = std::env::consts::EXE_SUFFIX)) + .collect(); + + assert_eq!(result, expected, "mismatch for case \"{request}\""); + } + + case( + "any", + &[ + "python", "python3", "cpython", "pypy", "graalpy", "cpython3", "pypy3", "graalpy3", + ], + ); + + case("default", &["python", "python3"]); + + case("3", &["python", "python3"]); + + case("4", &["python", "python4"]); + + case("3.13", &["python", "python3", "python3.13"]); + + case( + "pypy@3.10", + &[ + "python", + "python3", + "python3.10", + "pypy", + "pypy3", + "pypy3.10", + ], + ); + + case( + "3.13t", + &[ + "python", + "python3", + "python3.13", + "pythont", + "python3t", + "python3.13t", + ], + ); + + case( + "3.13.2", + &["python", "python3", "python3.13", "python3.13.2"], + ); + + case( + "3.13rc2", + &["python", "python3", "python3.13", "python3.13rc2"], + ); +} diff --git a/crates/uv-python/src/downloads.inc b/crates/uv-python/src/downloads.inc index 446e3be79..e730f6742 100644 --- a/crates/uv-python/src/downloads.inc +++ b/crates/uv-python/src/downloads.inc @@ -3,7 +3,8 @@ // Generated with `crates/uv-python/template-download-metadata.py` // From template at `crates/uv-python/src/downloads.inc.mustache` -use std::borrow::Cow; +use crate::PythonVariant; +use uv_pep440::{Prerelease, PrereleaseKind}; pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ ManagedPythonDownload { @@ -11,11 +12,349 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("e94fafbac07da52c965cb6a7ffc51ce779bd253cd98af801347aac791b96499f") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_64), + os: Os(target_lexicon::OperatingSystem::Darwin), + libc: Libc::None, + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("406664681bd44af35756ad08f5304f1ec57070bb76fae8ff357ff177f229b224") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("06e633164cb0133685a2ce14af88df0dbcaea4b0b2c5d3348d6b81393307481a") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("1b18f0eac4c3578ecca52ff388276546c701cea22410235716195c52ad7d0344") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("be2bbcb985ecf12eb7a16c18043a2b0b8551d8e8799c49a0d766b541dd465f47") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Powerpc64le), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("afe014200fea7505a67658fd82e70ccb49982deee752809849e781b941b941ec") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::S390x), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("b5782c027a8802b19656e961f73193cf060b124fd052dff19bb6d21b9e51ed14") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_64), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("b5e74d1e16402b633c6f04519618231fc0dbae7d2f9e4b1ac17c294cc3d3d076") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_64), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("10978500ab6589760716c644aeadffa0f2c0bf31ea10f0c6160fee933933a567") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), + os: Os(target_lexicon::OperatingSystem::Windows), + libc: Libc::None, + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("d5538ed2a247220516d4c14e8452f2c49318b29f8b524c908a1ed42e405bd8cc") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_64), + os: Os(target_lexicon::OperatingSystem::Windows), + libc: Libc::None, + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("c8134287496727922a5c47896b4f2b1623e3aab91cbb7c1ca64542db7593f3f1") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), + os: Os(target_lexicon::OperatingSystem::Darwin), + libc: Libc::None, + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("efc2e71c0e05bc5bedb7a846e05f28dd26491b1744ded35ed82f8b49ccfa684b") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_64), + os: Os(target_lexicon::OperatingSystem::Darwin), + libc: Libc::None, + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("2e07dfea62fe2215738551a179c87dbed1cc79d1b3654f4d7559889a6d5ce4eb") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-aarch64-unknown-linux-gnu-freethreaded%2Bdebug-full.tar.zst", + sha256: Some("3b2f53f544d1cb81520bd45446b68f7d51b5bd65e6a2a1422450fd9472c18e6c") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabi-freethreaded%2Bdebug-full.tar.zst", + sha256: Some("316cf6a946150ac6b42a1c43f18977966682b9d153c5891f717fba92f0cbe06f") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-armv7-unknown-linux-gnueabihf-freethreaded%2Bdebug-full.tar.zst", + sha256: Some("0b92de0f95fb304991749e5a7beb6778f961fbf0ce5057cc6e34d6754b0a0137") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Powerpc64le), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-ppc64le-unknown-linux-gnu-freethreaded%2Bdebug-full.tar.zst", + sha256: Some("2adcf07d6dea3a83747e24cd70721a15fd2b10a31e78314bd6782d3992b1670d") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::S390x), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-s390x-unknown-linux-gnu-freethreaded%2Bdebug-full.tar.zst", + sha256: Some("3b45d2be68ac66dde2d6cae55156806844f337063f475587c9fa023eea24460d") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_64), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + sha256: Some("a73adeda301ad843cce05f31a2d3e76222b656984535a7b87696a24a098b216c") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), + os: Os(target_lexicon::OperatingSystem::Windows), + libc: Libc::None, + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + sha256: Some("7794b0209af46b6347aab945f1ccc3b24add0a17b3f6fb7741447bc44d10bf4a") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_64), + os: Os(target_lexicon::OperatingSystem::Windows), + libc: Libc::None, + variant: PythonVariant::Freethreaded + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.13.0%2B20241016-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + sha256: Some("bfd89f9acf866463bc4baf01733da5e767d13f5d0112175a4f57ba91f1541310") + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 13, + patch: 0, + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), + os: Os(target_lexicon::OperatingSystem::Darwin), + libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("685ef71882f16eabab0bc838094727978370f0ad95c29f7f5c244ffa31316aeb") @@ -25,11 +364,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("0f5f9fcf82093c428b80c552165544439f4adcdbe5129ecf721d619e532e9b5e") @@ -39,11 +380,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("1414c6b37f37e8fd9d14e48d81e313eb9c965cb0330747d5d2d689dd7e0c7043") @@ -53,11 +396,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", sha256: Some("11befeaf4768c2ebbb258f5b07f94b7700f16424f858d6d2c250b434e99ce07c") @@ -67,11 +412,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", sha256: Some("b7180d5ea5fda2f397d04e2e6e11a2a7e0d732542bf54c484afb81d087a7b927") @@ -81,11 +428,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("59a2a81991d78bd658742d69b577a2b4c0734628ed42bff68615686eaf96f2ab") @@ -95,11 +444,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("2769182e58b0dddec15222bfeecbd4b12fde61c38f23a90aa942514f3545fb9b") @@ -109,11 +460,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("445156c61e1cc167f7b8777ad08cc36e5598e12cd27e07453f6e6dc0f62e421e") @@ -123,11 +476,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("4df6b7665c735a728d72e6f49034f1a6b7d9a54b0fbc472dc2ca525eb3dd513f") @@ -137,11 +492,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("873905b3e5e8cba700126e8d6ed28ad3aef0dd102f730f8ca196018477dd2da6") @@ -151,11 +508,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc3"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 3 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.13.0rc3%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("b59317828ef88f138ee122d420b60f2705bc72ae846ff69562e79e6c5cbc3177") @@ -165,11 +524,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("9e17f9fcc314a5dd489089a7502a525c4dd08af862f9cf33b52161a752f2a5b7") @@ -179,11 +540,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("971668ac7f3168efc4d2b589e9d36247ab8ca9f9525c56c8aa7bfd374060105b") @@ -193,11 +556,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("d99a663d3b9f8792a659e366372e685550045cad12aef11645c06a9b6edcd071") @@ -207,11 +572,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", sha256: Some("4ca7f2aeaabf8dbb2193f0fa86f869525a5c209eb403a39a73f4cf7040cf3613") @@ -221,11 +588,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", sha256: Some("0db2d263bdbb3af1e8dc0677fa44a5cda992ba989551346ccbbfd50a86135c3d") @@ -235,11 +604,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("70073333f7d3f0b900c7299659fec069bbefd5e04808b3729d2434b2232ac729") @@ -249,11 +620,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("50a2080e30d1504e76e5471e46830f0b4974c66b538ed8ec7df416975133ff89") @@ -263,11 +636,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("1893a218709d3664b7a2b80f5598b5f25c0c3fe2bcc8d0a1c75eec6bbb93d602") @@ -277,11 +652,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("6f09aa5ba6aab8bf21955dbc3d6bab19125130ef0ebe29242b0e5ac1eebb3161") @@ -291,11 +668,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("759f600b27a6a0ef2638cb02e8bbcc6de726dd1c896759f78da3e412f6c992e9") @@ -305,11 +684,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 13, patch: 0, - prerelease: Cow::Borrowed("rc2"), + prerelease: Some(Prerelease { kind: PrereleaseKind::Rc, number: 2 }), implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.13.0rc2%2B20240909-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("c883205751c714bd0519592673a88f160a55d34344cc1368353ad34a679eb94a") @@ -319,165 +700,189 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("473c8745464e2b20477bb4faa2e0de51b6fadae68ecd79ca7951df2d15c1b214") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("95dd397e3aef4cc1846867cf20be704bdd74edd16ea8032caf01e48f0c53d65d") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("acc9294db71a23be6ca947b039b1fc5be40638d2ab212034dba37abe4df9f53b") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("848405b92bda20fad1f9bba99234c7d3f11e0b31e46f89835d1cb3d735e932aa") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("5d2cf6bfd11351ba6d07a112ff563a62176a3f9f90a169f350f8c4d7489c08d5") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("c8f5ed70ee3c19da72d117f7b306adc6ca1eaf26afcbe1cc1be57d1e18df184c") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("92a84ee668037716bafad6e2872ee568c8480f45d5c5711b3a55c2a2e78bc5c5") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("d73cb8428a105d01141dee0ceec445328ab70e039e31cd8c5c1d7d226fb67afc") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("8d5e9053d89932734e77c312ba60f100413a19d54d377927424aebb0b0bfbad8") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("04b3087272d2bb8df98eec5fe81b666052907f292381cbecce17bec40fdd30c5") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("a8108eb85e3c33fd1bfb4ecee17cf8670b344e0dc54b93d7dcf2d31b7deecd46") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("922aa21fb9eacdd1c0a26ced4dca2725595453ae5b922d56b39ebdd2388175fd") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("dde181a2d7bcad9239b47d2e416f2fa0b0022052955227ab980ef6ca84a400c4") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("8e92d65b245b572fa6f520d428a9807a9da36428c7379a11d41ae428e69ed921") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("5ca4e88da44d5e07462a4a8b5ad27956dad9eedcfae4b1db37a784daf1827396") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("3a4d53a7ba3916c0c1f35cbbe57068e2571b138389f29cf5c35367fec8f4c617") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("a531873f3c382520e9f773ac3c84227e46ceb482b35a3d8703b4fab229096315") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("9314cb4d5aa525f2dc9f8d6ac204bebcfdfa8eb0dd4d3788af68769184355484") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("e55a74a174295a5fc32b0c3da07ef100bf27a99b381ed4e60f8bf981459eb63f") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("d7d7c897f11f12808d3fd9a0ce48e4de19369df4a9ee9390a4adae302902e333") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.12.7%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("cf4d81ba8e8b34c18d30d2de747c472b3d7a339063e71f2e6c910798469c3252") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.12.7%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("fa8ac308a7cd1774d599ad9a29f1e374fbdc11453b12a8c50cc4afdb5c4bfd1a") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("0419bafa4444a5aa0c554197bce0679e7cc0f28edc7ee8cfbe0ccea860bdb904") @@ -487,11 +892,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("b10d19eb5548a3b3b0a5e6f9109834d7ecfc139bc15754f81a94d39eaa5bdd26") @@ -501,11 +908,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("22d119ac7df7f0bddfd4dfd075bcc4eb2532ed3df0bdba0579106835d49ef9cd") @@ -515,11 +924,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", sha256: Some("190c23eb3b9c6b9638f69dc7fb829df8967ad64c82e82c93898a4d878d18ed2a") @@ -529,11 +940,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", sha256: Some("31a043c40e1dbb528404ff6e1fcad25638d54dfab2d379c3989d47ec24e6938b") @@ -543,11 +956,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("fb49374b512b0e9f2cd2a720b3836f8a04228d73eb0786e64221eb55979edc6e") @@ -557,11 +972,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("89be19666ecb7cdbbfd596e462d690a78a380f1fe5c2967b25a1779b0cec9339") @@ -571,11 +988,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("b080463e4f0c452e592cdac1ca97936a6a19bb3d9a64da669a50ca843fce0108") @@ -585,11 +1004,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("661e2a4b03d6eccbb5b15f5bd2869fbdd39132513394d758287e46115e48d4ef") @@ -599,11 +1020,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("d87275e613632ab738528fe20a94a7193e824e91ba7f1e7845e7fcfc1f114900") @@ -613,11 +1036,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240909/cpython-3.12.6%2B20240909-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("fe9898060f52c2171c2aa074f470f91339bdcf9896dae6709021c914f58aa863") @@ -627,11 +1052,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("90715cdab075e5a2680acf2695572d165b6269bdb5d1942ab577491478aea55f") @@ -641,11 +1068,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("49a9f7ad41d62e0ece9e664ca5ae95f022e7b68eef48e8a6f11620ec9247c686") @@ -655,11 +1084,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("06e512178cb513658a01c054b3eafc649ca362ccbeb02a6ae8a55b02c1ba75ca") @@ -669,11 +1100,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", sha256: Some("7a584de9c2824f43d7a7b1c26eb61a18af770ebd603a74b45d57601ba62ba508") @@ -683,11 +1116,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", sha256: Some("a9992b30d7b3ecb558cd12fde919e3e2836f161f8f777afea31140d5fff6362e") @@ -697,11 +1132,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("3bea081f4e6fa67e600a6a791bcfebb2891531ede2c21e23e1b7321b3369c737") @@ -711,11 +1148,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("2b6ea3a5242de99574191ee42df864756eca6d7cb1dbd4cd7ab2850ba8b828f8") @@ -725,11 +1164,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("10680b593b5e31833218fd83104dee74af970a3463403a22bae613b952a34e8d") @@ -739,11 +1180,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("e61b1274e1195f227cb30ba5d89ea32d743796d992adcaffad4819e4b0405d24") @@ -753,11 +1196,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("b1009d46b87330c099d02411ca5e9e333f13305c5abdbe20810a7c467cedb051") @@ -767,11 +1212,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.12.5%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("6eb0398795e8875575934cf21cdc9c7c7acddb46f9a52f91fdad509723f2f0e9") @@ -781,11 +1228,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("ef6948e836f531bd7a58ffbe602803ff1c83c65f99d1da19be369ea61f136c93") @@ -795,11 +1244,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("9d68cbdd12d1d6f98d35cc76add232c12db75c6b7f49733bffc88e7b1c025a79") @@ -809,11 +1260,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("6c9cf13644edc7250525ab1b2529ba1c0fff56c0c5a5c2242d84b6d4889d2bea") @@ -823,11 +1276,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", sha256: Some("5a23ed8eaf948fe48d7c05dbfb58ea8638dcd2c4880d8519e069281ab427cbcb") @@ -837,11 +1292,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", sha256: Some("4281764e69339a138e30211b9923d74036d07c7a56c6aacc6dbdb2802a575f51") @@ -851,11 +1308,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("35a8359f1dc17a7a70007dae102a5e1562c0715a721377ede92137b2a0292406") @@ -865,11 +1324,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("b2fd015ab3689e024de6fbb34a4942acdb54c2184d1963e22829aafa1d81ba2c") @@ -879,11 +1340,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("ca076aee4329f53f988346eb0521ad2a2cf7f723b6296088d03b98d8f22f5420") @@ -893,11 +1356,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("de4983ffa610ff2c3b9bcb62882366f017d94bf11b194c1fce17ad9e502acce6") @@ -907,11 +1372,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("ff0fab24f38c22130e45b90b7ec10dc4ce9677b545d9fb9109a72d2ffbab7b02") @@ -921,11 +1388,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240726/cpython-3.12.4%2B20240726-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("6dd7b4607f8a25f0f5f68e745f4c572b1a20c3bbfa86accfa45b52ab93b18ece") @@ -935,11 +1404,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("ccc40e5af329ef2af81350db2a88bbd6c17b56676e82d62048c15d548401519e") @@ -949,11 +1420,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("c37a22fca8f57d4471e3708de6d13097668c5f160067f264bb2b18f524c890c8") @@ -963,11 +1436,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("ec8126de97945e629cca9aedc80a29c4ae2992c9d69f2655e27ae73906ba187d") @@ -977,11 +1452,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-armv7-unknown-linux-gnueabi-install_only.tar.gz", sha256: Some("f693dd22b69361c17076157889eb8f1ce1a5ea670c031fae46782481ad892a64") @@ -991,11 +1468,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-armv7-unknown-linux-gnueabihf-install_only.tar.gz", sha256: Some("635080827bed4616dc271545677837203098e5b55e7195d803e1dca7da24fc0c") @@ -1005,11 +1484,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c5dcf08b8077e617d949bda23027c49712f583120b3ed744f9b143da1d580572") @@ -1019,11 +1500,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("872fc321363b8cdd826fd2cb1adfd1ceb813bc1281f9d410c1c2c4e177e8df86") @@ -1033,11 +1516,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("a73ba777b5d55ca89edef709e6b8521e3f3d4289581f174c8699adfb608d09d6") @@ -1047,11 +1532,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("eb70814dc254f02714c77305de01b8ed2250c146320e22d0ed14b39021f89a8a") @@ -1061,11 +1548,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-i686-pc-windows-msvc-install_only.tar.gz", sha256: Some("bd723ad1aa05551627715a428660250f0e74db0f1421b03f399235772057ef55") @@ -1075,11 +1564,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3%2B20240415-x86_64-pc-windows-msvc-install_only.tar.gz", sha256: Some("f7cfa4ad072feb4578c8afca5ba9a54ad591d665a441dd0d63aa366edbe19279") @@ -1089,11 +1580,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("01c064c00013b0175c7858b159989819ead53f4746d40580b5b0b35b6e80fba6") @@ -1103,11 +1596,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("a53a6670a202c96fec0b8c55ccc780ea3af5307eb89268d5b41a9775b109c094") @@ -1117,11 +1612,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("e52550379e7c4ac27a87de832d172658bc04150e4e27d4e858e6d8cbb96fd709") @@ -1131,11 +1628,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("74bc02c4bbbd26245c37b29b9e12d0a9c1b7ab93477fed8b651c988b6a9a6251") @@ -1145,11 +1644,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("ecd6b0285e5eef94deb784b588b4b425a15a43ae671bf206556659dc141a9825") @@ -1159,11 +1660,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("57a37b57f8243caa4cdac016176189573ad7620f0b6da5941c5e40660f9468ab") @@ -1173,11 +1676,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("b428b4151c70b85339ac2659e5f69f7e47142d34a506e05ecd095efe2e3dec81") @@ -1187,11 +1692,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("1e919365f3e04eb111283f7a45d32eac2f327287ab7bf46720d5629e144cbff9") @@ -1201,11 +1708,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("1e5655a6ccb1a64a78460e4e3ee21036c70246800f176a6c91043a3fe3654a3b") @@ -1215,11 +1724,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("f93f8375ca6ac0a35d58ff007043cbd3a88d9609113f1cb59cf7c8d215f064af") @@ -1229,11 +1740,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("eca96158c1568dedd9a0b3425375637a83764d1fa74446438293089a8bfac1f8") @@ -1243,11 +1756,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("236533ef20e665007a111c2f36efb59c87ae195ad7dca223b6dc03fb07064f0b") @@ -1257,11 +1772,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("78051f0d1411ee62bc2af5edfccf6e8400ac4ef82887a2affc19a7ace6a05267") @@ -1271,11 +1788,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("60631211c701f8d2c56e5dd7b154e68868128a019b9db1d53a264f56c0d4aee2") @@ -1285,11 +1804,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("74e330b8212ca22fd4d9a2003b9eec14892155566738febc8e5e572f267b9472") @@ -1299,11 +1820,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("876389f071d62ee9a4bdd7ce31e69c3cdd256fe498e4dd6bb2b80e674e7351fe") @@ -1313,11 +1836,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("13c8a6f337a4e1ef043ffb8ea3c218ab2073afe0d3be36fcdf8ceb6f757210e8") @@ -1327,11 +1852,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("fd5a9e0f41959d0341246d3643f2b8794f638adc0cec8dd5e1b6465198eae08a") @@ -1341,11 +1868,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("4734a2be2becb813830112c780c9879ac3aff111a0b0cd590e65ec7465774d02") @@ -1355,11 +1884,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("5a9e88c8aa52b609d556777b52ebde464ae4b4f77e4aac4eb693af57395c9abf") @@ -1369,11 +1900,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("bccfe67cf5465a3dfb0336f053966e2613a9bc85a6588c2fcf1366ef930c4f88") @@ -1383,11 +1916,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("b5dae075467ace32c594c7877fe6ebe0837681f814601d5d90ba4c0dfd87a1f2") @@ -1397,11 +1932,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("5681621349dd85d9726d1b67c84a9686ce78f72e73a6f9e4cc4119911655759e") @@ -1411,11 +1948,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("e51a5293f214053ddb4645b2c9f84542e2ef86870b8655704367bd4b29d39fe9") @@ -1425,11 +1964,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("922f9404f39dc4edb8558a93cef5c3330895a4c87acb1de2a2cf662ab942dbe5") @@ -1439,11 +1980,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("6e4f30a998245cfaef00d1b87f8fd5f6c250bd222f933f8f38f124d4f03227f9") @@ -1453,11 +1996,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 12, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.12.0%2B20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("facfaa1fbc8653f95057f3c4a0f8aa833dab0e0b316e24ee8686bc761d4b4f8d") @@ -1467,165 +2012,189 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("263b1b50ff705ab064c50c3165de9df2ed712a77cd81c55dd340c3172e103687") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("a5a224138a526acecfd17210953d76a28487968a767204902e2bde809bb0e759") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("a8174aef8a662201d1bb4680571cf383e89f5b44dc06614df5928595ddf82876") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("575b49a7aa64e97b06de605b7e947033bf2310b5bc5f9aedb9859d4745033d91") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("723ec57b64349d748bbd55579be8a08c0d3633267f0c5820eb75df598792c975") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("9d124604ffdea4fbaabb10b343c5a36b636a3e7b94dfc1cccd4531f33fceae5e") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("bb3bd40fd5f0490534d52f8d465af6b91a7e0e7291d020d9bbd95350a0638238") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("deb089a5ac0fbd9ad2e3dc843d90019ead75b1ec895fd57a5abca190ba86cb77") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("5f2b7e6b087e085ad6bfd6a43a49611ffcc47307e45bc65f8963717b2a59dc4c") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("3655da6f1ccde823fc03f790bebfff106825e2b5ec4b733be225150275cd6321") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("34a2f075aba20e65cfc1973ef0108c369ac89564bae93506c49257215d3555a2") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("cc16cf0b1a1aa61f4e90d38ccaad0b65085cea69d2dcc2c6281ef9d4e6cccdd8") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("317320957770a4cdbe42f3810a67685045e1cd82adf545948ec64d9d566d0c4f") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("e8017e3b916f8c7b8fbdf2bd5fc18c6eb7ce2397df240fbeea84b05d4c7a37a4") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("34c58f0dfd84ce2b16cea07ca39c47ed8d7b7b3656dda63062e9e9db55d43ec4") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("03f15e19e2452641b6375b59ba094ff6cf2fc118315d24a6ca63ce60e4d4a6e0") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("b0c62b085e4adb7cc32be3a7dd267bc427c2ab588802f2918d90873ece382342") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("5b33f0ff29552f15daacf81c426ed585fae24987b47d614142a7906eae6f2b04") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("4840b2fb1d421a38d8ffabf80350093e4283a67e85e67ba8285170b44b371ad9") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("0a5b423517722e9868ac4a63893f24f24db9bd67e8679e6e448343c5829d2e77") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.11.10%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("15a14bdc15a43119d74b7e3bde00031011d49d2a6068fe9743da92ba0c3d7b87") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.11.10%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("ea770ebabc620ff46f1d0f905c774a9b8aa5834620e89617ad5e01f90d36b3ee") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("c4e2f7774421bcb381245945e132419b529399dfa4a56059acda1493751fa377") @@ -1635,11 +2204,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("c8680f90137e36b54b3631271ccdfe5de363e7d563d8df87c53e11b956a00e04") @@ -1649,11 +2220,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("364cf099524fff92c31b8ff5ae3f7b32b0fa6cf1d380c6e37cf56140d08dfc87") @@ -1663,11 +2236,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", sha256: Some("e64d3cf033c804e9c14aaf4ae746632c01894706098b20acbf00df4bd28d0b0e") @@ -1677,11 +2252,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", sha256: Some("7630838c7602e6a6a56c41263d6a808a2a2004a7ea38770ffc4c7aaf34e169ae") @@ -1691,11 +2268,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("2387479d17127e5b087f582bac948f859c25c4b38c64f558e0a399af7a8a0225") @@ -1705,11 +2284,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("30c71053e9360471b7f350f1562ff4e42eb91ad2ca61b391295b5dea8b2b9efd") @@ -1719,11 +2300,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("daa487c7e73005c4426ac393273117cf0e2dc4ab9b2eeda366e04cd00eea00c9") @@ -1733,11 +2316,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("b3e94cbf19bd08bf02f6e6945f6c2211453f601c7c6f79721da63a06bf99b1f9") @@ -1747,11 +2332,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("091c99a210f4f401a305231f3f218ee3d5714658b8d3aac344d34efc716dff85") @@ -1761,11 +2348,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.11.9%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("8ac54a8d711ef0d49b62a2c3521c2d0403f1b221dc9d84c5f85fe48903e82523") @@ -1775,11 +2364,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("389a51139f5abe071a0d70091ca5df3e7a3dfcfcbe3e0ba6ad85fb4c5638421e") @@ -1789,11 +2380,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("097f467b0c36706bfec13f199a2eaf924e668f70c6e2bd1f1366806962f7e86e") @@ -1803,11 +2396,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("389b9005fb78dd5a6f68df5ea45ab7b30d9a4b3222af96999e94fd20d4ad0c6a") @@ -1817,11 +2412,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("eb2b31f8e50309aae493c6a359c32b723a676f07c641f5e8fe4b6aa4dbb50946") @@ -1831,11 +2428,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("844f64f4c16e24965778281da61d1e0e6cd1358a581df1662da814b1eed096b9") @@ -1845,11 +2444,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("94e13d0e5ad417035b80580f3e893a72e094b0900d5d64e7e34ab08e95439987") @@ -1859,11 +2460,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("08e1ebf51b5965e23f8e68664d17274c1cdabb5b2d7509a2003920e5d58172c7") @@ -1873,11 +2476,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("75039951f8f94d7304bc17b674af1668b9e1ea6d6c9ba1da28e90c0ad8030e3c") @@ -1887,11 +2492,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("b618f1f047349770ee1ef11d1b05899840abd53884b820fd25c7dfe2ec1664d4") @@ -1901,11 +2508,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("b042c966920cf8465385ca3522986b12d745151a72c060991088977ca36d3883") @@ -1915,11 +2524,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("a0e615eef1fafdc742da0008425a9030b7ea68a4ae4e73ac557ef27b112836d4") @@ -1929,11 +2540,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("b102eaf865eb715aa98a8a2ef19037b6cc3ae7dfd4a632802650f29de635aa13") @@ -1943,11 +2556,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("b44e1b74afe75c7b19143413632c4386708ae229117f8f950c2094e9681d34c7") @@ -1957,11 +2572,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("49520e3ff494708020f306e30b0964f079170be83e956be4504f850557378a22") @@ -1971,11 +2588,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("4a51ce60007a6facf64e5495f4cf322e311ba9f39a8cd3f3e4c026eae488e140") @@ -1985,11 +2604,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("1a919a35172eb9419eba841eeb0ec9879dbc2b006b284ee5c454c08197b50f74") @@ -1999,11 +2620,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("f5a6ca1280749d8ceaf8851585ef6b0cd2f1f76e801a77c1d744019554eef2f0") @@ -2013,11 +2636,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.11.7%2B20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("67077e6fa918e4f4fd60ba169820b00be7c390c497bf9bc9cab2c255ea8e6f3e") @@ -2027,11 +2652,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("916c35125b5d8323a21526d7a9154ca626453f63d0878e95b9f613a95006c990") @@ -2041,11 +2668,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("178cb1716c2abc25cb56ae915096c1a083e60abeba57af001996e8bc6ce1a371") @@ -2055,11 +2684,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("3e26a672df17708c4dc928475a5974c3fb3a34a9b45c65fb4bd1e50504cc84ec") @@ -2069,11 +2700,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("7937035f690a624dba4d014ffd20c342e843dd46f89b0b0a1e5726b85deb8eaf") @@ -2083,11 +2716,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("f9f19823dba3209cedc4647b00f46ed0177242917db20fb7fb539970e384531c") @@ -2097,11 +2732,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("ee37a7eae6e80148c7e3abc56e48a397c1664f044920463ad0df0fc706eacea8") @@ -2111,11 +2748,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("c929e5fe676ad20afcf6807a797d21261ae0827e84ec18742031a9582aed0d46") @@ -2125,11 +2764,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("dd48b2cfaae841b4cd9beed23e2ae68b13527a065ef3d271d228735769c4e64d") @@ -2139,11 +2780,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20231002/cpython-3.11.6%2B20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("3933545e6d41462dd6a47e44133ea40995bc6efeed8c2e4cbdf1a699303e95ea") @@ -2153,11 +2796,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("dab64b3580118ad2073babd7c29fd2053b616479df5c107d31fe2af1f45e948b") @@ -2167,11 +2812,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("4a4efa7378c72f1dd8ebcce1afb99b24c01b07023aa6b8fea50eaedb50bf2bfc") @@ -2181,11 +2828,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("bb5c5d1ea0f199fe2d3f0996fff4b48ca6ddc415a3dbd98f50bff7fce48aac80") @@ -2195,11 +2844,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("82de7e2551c015145c017742a5c0411d67a7544595df43c02b5efa4762d5123e") @@ -2209,11 +2860,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("14121b53e9c8c6d0741f911ae00102a35adbcf5c3cdf732687ef7617b7d7304d") @@ -2223,11 +2876,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("fe459da39874443579d6fe88c68777c6d3e331038e1fb92a0451879fb6beb16d") @@ -2237,11 +2892,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("fbed6f7694b2faae5d7c401a856219c945397f772eea5ca50c6eb825cbc9d1e1") @@ -2251,11 +2908,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("fe09ecd87f69a724acf26ca508d7ead91a951abb2da18dfb98fe22c284454121") @@ -2265,11 +2924,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("936b624c2512a3a3370aae8adf603d6ae71ba8ebd39cc4714a13306891ea36f0") @@ -2279,11 +2940,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("00f002263efc8aea896bcfaaf906b1f4dab3e5cd3db53e2b69ab9a10ba220b97") @@ -2293,11 +2956,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("cb6d2948384a857321f2aa40fa67744cd9676a330f08b6dad7070bda0b6120a4") @@ -2307,11 +2972,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("47e1557d93a42585972772e82661047ca5f608293158acb2778dccf120eabb00") @@ -2321,11 +2988,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("2e84fc53f4e90e11963281c5c871f593abcb24fc796a50337fa516be99af02fb") @@ -2335,11 +3004,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("abdccc6ec7093f49da99680f5899a96bff0b96fde8f5d73f7aac121e0d05fdd8") @@ -2349,11 +3020,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("df7b92ed9cec96b3bb658fb586be947722ecd8e420fb23cee13d2e90abcfcf25") @@ -2363,11 +3036,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("e477f0749161f9aa7887964f089d9460a539f6b4a8fdab5166f898210e1a87a4") @@ -2377,11 +3052,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("e26247302bc8e9083a43ce9e8dd94905b40d464745b1603041f7bc9a93c65d05") @@ -2391,11 +3068,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("1218ca44595aeaf34271508db64a2abc581c3ee1eb307c1b0537ea746922b806") @@ -2405,11 +3084,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("e2f4b41c3d89c5ec735e2563d752856cb3c19a0aa712ec7ef341712bafa7e905") @@ -2419,11 +3100,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.11.4%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("878614c03ea38538ae2f758e36c85d2c0eb1eaaca86cd400ff8c76693ee0b3e1") @@ -2433,11 +3116,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("09e412506a8d63edbb6901742b54da9aa7faf120b8dbdce56c57b303fc892c86") @@ -2447,11 +3132,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("f710b8d60621308149c100d5175fec39274ed0b9c99645484fd93d1716ef4310") @@ -2461,11 +3148,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("8190accbbbbcf7620f1ff6d668e4dd090c639665d11188ce864b62554d40e5ab") @@ -2475,11 +3164,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("36ff6c5ebca8bf07181b774874233eb37835a62b39493f975869acc5010d839d") @@ -2489,11 +3180,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("767d24f3570b35fedb945f5ac66224c8983f2d556ab83c5cfaa5f3666e9c212c") @@ -2503,11 +3196,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("da50b87d1ec42b3cb577dfd22a3655e43a53150f4f98a4bfb40757c9d7839ab5") @@ -2517,11 +3212,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("82eed5ae1ca9e60ed9b9cac97e910927ffe2e80e91161c74b2d70e44d5227de0") @@ -2531,11 +3228,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("a6751e6fa5c7c4d4748ed534a7f00ad7f858f62ce73d63d44dd907036ba53985") @@ -2545,11 +3244,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.11.3%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("24741066da6f35a7ff67bee65ce82eae870d84e1181843e64a7076d1571e95af") @@ -2559,11 +3260,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("4918cdf1cab742a90f85318f88b8122aeaa2d04705803c7b6e78e81a3dd40f80") @@ -2573,11 +3276,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("20a4203d069dc9b710f70b09e7da2ce6f473d6b1110f9535fb6f4c469ed54733") @@ -2587,11 +3292,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("debf15783bdcb5530504f533d33fda75a7b905cec5361ae8f33da5ba6599f8b4") @@ -2601,11 +3308,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("8392230cf76c282cfeaf67dcbd2e0fac6da8cd3b3aead1250505c6ddd606caae") @@ -2615,11 +3324,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("02a551fefab3750effd0e156c25446547c238688a32fabde2995c941c03a6423") @@ -2629,11 +3340,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("7f0425d3e9b2283aba205493e9fe431bc2c2d67cc369bc922825b827a1b06b82") @@ -2643,11 +3356,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("50b250dd261c3cca9ae8d96cb921e4ffbc64f778a198b6f8b8b0a338f77ae486") @@ -2657,11 +3372,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 11, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1%2B20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("edc08979cb0666a597466176511529c049a6f0bba8adf70df441708f766de5bf") @@ -2671,165 +3388,189 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("1f753073b2333b2977922387fe016255ebde0f8558c567ffb960b7f50477fd34") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("fa79bd909bfeb627ffe66a8b023153495ece659e5e3b2ff56268535024db851c") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("18dd6c0c219da1ec7daede9da923205594dbdc7f37a283873363b4634a5e3f8f") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("0d952fa2342794523ea7beee6a58e79e62045d0f018314ce282e9f2f1427ee2c") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("2773766d4dbe106a61031e38fd844f64886de93b7ae4389e0af52f7dc77092c3") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("6008b42df79a0c8a4efe3aa88c2aea1471116aa66881a8ed15f04d66438cb7f5") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("eea8a6f99588e5bae2ed9ba3686a0ee095aee46058876d990a8b4fc87309448b") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("38daa81e0cbdc199d69241c35855dd05709f8246484cfe66b84666e123abb7df") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("feab385a00f1312f318097521263cb17a68d4ce9c8945cfb97c07db3965838ca") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("af28aab17dd897d14ae04955b19be3080fbaa6778a251943d268bc597ac39427") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("2f90e76f5080cbc221447a41e7ec7949318c62c0e8e78ca919ed2f56669d8261") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("4b86196b928b51ef3a0d51aa1690236e3da4561e34254e2929c0fcd37b37a002") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("14f8ed8e42db2296723f3e64e30e24ce2e27fc36534dcb77f474c8edb1fde872") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("fbac57f67ca8a684f0442ff73c511efc177850c48f508f23521a816eae34d75f") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("db58da5c9888a0dd7036d76ae4babd62f26ba74530a528b684af6f4688b86583") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("25fb8e23cd3b82b748075a04fd18f3183cc7316c11d6f59eb4b0326843892600") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("a4eec7b3cbf5f2292f2d5ef0181317430a2e053d811bf8547918c27ded589416") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("a169bdcd98f62421062fb9066763495913f4a86ee88c7d36e51df86d5d3cbe62") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("29ea71edc4899061a9c3d2fd85773c1ae5d353ea9cb39c3d6b3e4c2a9bf4fda0") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("976d1560a02f2b921668fafc76196c1ff1bb24ccaa76ed5567539fb6dab0aa5a") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.10.15%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("50a39a5a09dd8786b154b116dbdbf7d6292fdf63b1683f6006d43246c0b9aad9") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.10.15%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("45a95225c659f9b988f444d985df347140ecc71c0297c6857febf5ef440d689a") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("f7ca9bffbce433c8d445edd33a5424c405553d735efee65a2fc5d8bbb1c8e137") @@ -2839,11 +3580,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("4404f44ec69c0708d4d88e98f39c2c1fe3bd462dc6a958b60aaf63028550c485") @@ -2853,11 +3596,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("0ffe64c77cacda7e3afcb0d8ba271c59ca0a30dfda218da39a573b412bb4afd7") @@ -2867,11 +3612,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", sha256: Some("451449f18a49e6ceecf9c1f70f4aee0d1552eff103c3db291319125238182c9d") @@ -2881,11 +3628,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", sha256: Some("7f215b85df78c568847329faeb2c5007c301741d9c4ccebbd935a3a2963197b5") @@ -2895,11 +3644,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("8b83fdd95cb864f8ebfa1a1dd7e700bb046b8283bfd0a3aa04f1ff259eaff99e") @@ -2909,11 +3660,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("ff1c4f010b1c6f563c71fa30f68293168536e0ed65f7d470a7e8c73252d08653") @@ -2923,11 +3676,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("159c456bb4a3802bafbce065ff54b99ddb16422500d75c1315573ee3b673af17") @@ -2937,11 +3692,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("8803a748f2197ec2360af6feebe9c936f4f6beabcae1db5557fdd98fc922982c") @@ -2951,11 +3708,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("a84742f13584fd39f4f4b0d9a5865621a3c88cad91b31f17f414186719063364") @@ -2965,11 +3724,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.10.14%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("61ad1abcaca639eecb5bd0b129ac0315d79f7b90cf0aca8e9fb85c9e7269c26b") @@ -2979,11 +3740,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("5fdc0f6a5b5a90fd3c528e8b1da8e3aac931ea8690126c2fdb4254c84a3ff04a") @@ -2993,11 +3756,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("6378dfd22f58bb553ddb02be28304d739cd730c1f95c15c74955c923a1bc3d6a") @@ -3007,11 +3772,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("a898a88705611b372297bb8fe4d23cc16b8603ce5f24494c3a8cfa65d83787f9") @@ -3021,11 +3788,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.10.13%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("424d239b6df60e40849ad18505de394001233ab3d7470b5280fec6e643208bb9") @@ -3035,11 +3804,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c23706e138a0351fc1e9def2974af7b8206bac7ecbbb98a78f5aa9e7535fee42") @@ -3049,11 +3820,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("09be8fb2cdfbb4a93d555f268f244dbe4d8ff1854b2658e8043aa4ec08aede3e") @@ -3063,11 +3836,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("d995d032ca702afd2fc3a689c1f84a6c64972ecd82bba76a61d525f08eb0e195") @@ -3077,11 +3852,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("48365ea10aa1b0768a153bfff50d1515a757d42409b02a4af4db354803f2d180") @@ -3091,11 +3868,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("5365b90f9cba7186d12dd86516ece8b696db7311128e0b49c92234e01a74599f") @@ -3105,11 +3884,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("086f7fe9156b897bb401273db8359017104168ac36f60f3af4e31ac7acd6634e") @@ -3119,11 +3900,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("bc66c706ea8c5fc891635fda8f9da971a1a901d41342f6798c20ad0b2a25d1d6") @@ -3133,11 +3916,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("8a6e3ed973a671de468d9c691ed9cb2c3a4858c5defffcf0b08969fba9c1dd04") @@ -3147,11 +3932,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("fee80e221663eca5174bd794cb5047e40d3910dbeadcdf1f09d405a4c1c15fe4") @@ -3161,11 +3948,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c7a5321a696ef6467791312368a04d36828907a8f5c557b96067fa534c716c18") @@ -3175,11 +3964,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("bb5e8cb0d2e44241725fa9b342238245503e7849917660006b0246a9c97b1d6c") @@ -3189,11 +3980,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("8d33d435ae6fb93ded7fc26798cc0a1a4f546a4e527012a1e2909cc314b332df") @@ -3203,11 +3996,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("a476dbca9184df9fc69fe6309cda5ebaf031d27ca9e529852437c94ec1bc43d3") @@ -3217,11 +4012,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("9080014bee2d4bd1f96bcbebf447d40c35ae9354382246add1160bd0d433ebf7") @@ -3231,11 +4028,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("a5a5f9c9082b6503462a6b134111d3c303052cbc49ff31fff2ade38b39978e5d") @@ -3245,11 +4044,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.10.12%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("c1a31c353ca44de7d1b1a3b6c55a823e9c1eed0423d4f9f66e617bdb1b608685") @@ -3259,11 +4060,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("8348bc3c2311f94ec63751fb71bd0108174be1c4def002773cf519ee1506f96f") @@ -3273,11 +4076,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("bd3fc6e4da6f4033ebf19d66704e73b0804c22641ddae10bbe347c48f82374ad") @@ -3287,11 +4092,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c7573fdb00239f86b22ea0e8e926ca881d24fde5e5890851339911d76110bc35") @@ -3301,11 +4108,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c70518620e32b074b1b40579012f0c67191a967e43e84b8f46052b6b893f7eeb") @@ -3315,11 +4124,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("73a9d4c89ed51be39dd2de4e235078281087283e9fdedef65bec02f503e906ee") @@ -3329,11 +4140,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c5bcaac91bc80bfc29cf510669ecad12d506035ecb3ad85ef213416d54aecd79") @@ -3343,11 +4156,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("c5dde3276541a8ad000ba631ec70012aa2261926c13f54d2b1de83dad61d59c1") @@ -3357,11 +4172,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("e4ed3414cd0e687017f0a56fed88ff39b3f5dfb24a0d62e9c7ca55854178bcde") @@ -3371,11 +4188,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("9c2d3604a06fcd422289df73015cd00e7271d90de28d2c910f0e2309a7f73a68") @@ -3385,11 +4204,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("018d05a779b2de7a476f3b3ff2d10f503d69d14efcedd0774e6dab8c22ef84ff") @@ -3399,11 +4220,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("0e685f98dce0e5bc8da93c7081f4e6c10219792e223e4b5886730fd73a7ba4c6") @@ -3413,11 +4236,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("2003750f40cd09d4bf7a850342613992f8d9454f03b3c067989911fb37e7a4d1") @@ -3427,11 +4252,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("44566c08eb8054aa0784f76b85d2c6c70a62f4988d5e9abcce819b517b329fdd") @@ -3441,11 +4268,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("d196347aeb701a53fe2bb2b095abec38d27d0fa0443f8a1c2023a1bed6e18cdf") @@ -3455,11 +4284,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("cf17e6d042777170e423c6b80e096ad8273d9848708875db0d23dd45bdb3d516") @@ -3469,11 +4300,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("c5c51d9a3e8d8cdac67d8f3ad7c4008de169ff1480e17021f154d5c99fcee9e3") @@ -3483,11 +4316,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.10.9%2B20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("59c6970cecb357dc1d8554bd0540eb81ee7f6d16a07acf3d14ed294ece02c035") @@ -3497,11 +4332,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("d52b03817bd245d28e0a8b2f715716cd0fcd112820ccff745636932c76afa20a") @@ -3511,11 +4348,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("525b79c7ce5de90ab66bd07b0ac1008bafa147ddc8a41bef15ffb7c9c1e9e7c5") @@ -3525,11 +4364,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("33170bef18c811906b738be530f934640491b065bf16c4d276c6515321918132") @@ -3539,11 +4380,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("2deee7cbbd5dad339d713a75ec92239725d2035e833af5b9981b026dee0b9213") @@ -3553,11 +4396,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("6c8db44ae0e18e320320bbaaafd2d69cde8bfea171ae2d651b7993d1396260b7") @@ -3567,11 +4412,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("9f035bbe53f55fb406f95cb68459ba245b386084eeb5760f1660f416b730328d") @@ -3581,11 +4428,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("94e76273166f72624128e52b5402db244cea041dab4a6bcdc70b304b66e27e95") @@ -3595,11 +4444,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.10.8%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("f2b6d2f77118f06dd2ca04dae1175e44aaa5077a5ed8ddc63333c15347182bfe") @@ -3609,11 +4460,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("70f6ca1da8e6fce832ad0b7f9fdaba0b84ba0ac0a4c626127acb6d49df4b8f91") @@ -3623,11 +4476,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("6101f580434544d28d5590543029a7c6bdf07efa4bcdb5e4cbedb3cd83241922") @@ -3637,11 +4492,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("dfeec186a62a6068259d90e8d77e7d30eaf9c2b4ae7b205ff8caab7cb21f277c") @@ -3651,11 +4508,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("4a611ce990dc1f32bc4b35d276f04521464127f77e1133ac5bb9c6ba23e94a82") @@ -3665,11 +4524,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c12c9ad2b2c75464541d897c0528013adecd8be5b30acf4411f7759729841711") @@ -3679,11 +4540,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("3e0cab6e49ad5ef95851049463797ec713eee6e1f2fa1d99e30516d37797c3f0") @@ -3693,11 +4556,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("384e711dd657c3439be4e50b2485478a7ed7a259a741d4480fc96d82cc09d318") @@ -3707,11 +4572,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.10.7%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("b464352f8cbf06ab4c041b7559c9bda7e9f6001a94f67ab0a342cba078f3805f") @@ -3721,11 +4588,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("efaf66acdb9a4eb33d57702607d2e667b1a319d58c167a43c96896b97419b8b7") @@ -3735,11 +4604,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("7718411adf3ea1480f3f018a643eb0550282aefe39e5ecb3f363a4a566a9398c") @@ -3749,11 +4620,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("81625f5c97f61e2e3d7e9f62c484b1aa5311f21bd6545451714b949a29da5435") @@ -3763,11 +4636,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("b152801a2609e6a38f3cc9e7e21d8b6cf5b6f31dacfcaca01e162c514e851ed6") @@ -3777,11 +4652,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("55aa2190d28dcfdf414d96dc5dcea9fe048fadcd583dc3981fec020869826111") @@ -3791,11 +4668,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("8cafe6409e9d192b288b84a21bc0c309f1d3f6b809a471b2858c7bf1bb09f3a7") @@ -3805,11 +4684,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("27f22babf29ceebae18b2c2e38e2c48d22de686688c8a31c5f8d7d51541583c1") @@ -3819,11 +4700,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.10.6%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("91889a7dbdceea585ff4d3b7856a6bb8f8a4eca83a0ff52a73542c2e67220eaa") @@ -3833,11 +4716,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("19d1aa4a6d9ddb0094fc36961b129de9abe1673bce66c86cd97b582795c496a8") @@ -3847,11 +4732,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("eca0584397d9a3ef6f7bb32b0476318b01c89b7b0a031ef97a0dcaa5ba5127a8") @@ -3861,11 +4748,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("012fa37c12d2647d76d004dc003302563864d2f1cd0731b71eeafad63d28b3f0") @@ -3875,11 +4764,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("5abf5baf40f8573ce7d7e4ad323457f511833e1663e61ac5a11d5563a735159f") @@ -3889,11 +4780,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("460f87a389be28c953c24c6f942f172f9ce7f331367b4daf89cb450baedd51d7") @@ -3903,11 +4796,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("6aad42c7b03989173dd0e4d066e8c1e9f176f4b31d5bde26dbb5297f38f656d0") @@ -3917,11 +4812,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("2846e9c7e8484034989ab218022009fdd9dcb12a7bfb4b0329a404552d37e9aa") @@ -3931,11 +4828,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220630/cpython-3.10.5%2B20220630-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("c830ab2a3a488f9cf95e4e81c581d9ef73e483c2e6546136379443e9bb725119") @@ -3945,11 +4844,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("6d2e4e6b1c403bce84cfb846400754017f525fe8017f186e8e7072fcaaf3aa71") @@ -3959,11 +4860,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("c4a57a13b084d49ce8c2eb5b2662ee45b0c55b08ddd696f473233b0787f03988") @@ -3973,11 +4876,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("7a8989392dc9b41d85959a752448c60852cf0061de565e98445c27f6bbdf63be") @@ -3987,11 +4892,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("f3bc0828a0e0a8974e3fe90b4e99549296a7578de2321d791be1bad28191921d") @@ -4001,11 +4908,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("1f8423808ad84c0e56c8e14c32685cbfbc1159e0d9f943ac946f29e84cf1b5ee") @@ -4015,11 +4924,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("74c8da0aa24233c76bdd984d3c9e44442eca316be8a2cb4972d9264fedb0d5e8") @@ -4029,11 +4940,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("e1dfa5dde910f908cad8bd688b29d28df832f7b150555679c204580d1af0c4a6") @@ -4043,11 +4956,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220528/cpython-3.10.4%2B20220528-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("7231ba2af9525cae620a5f4ae3bf89a939fdc053ba0cc64ee3dead8f13188005") @@ -4057,11 +4972,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("db46dadfccc407aa1f66ed607eefbf12f781e343adcb1edee0a3883d081292ce") @@ -4071,11 +4988,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("ec2e90b6a589db7ef9f74358b1436558167629f9e4d725c8150496f9cb08a9d4") @@ -4085,11 +5004,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("f52ee68c13c4f9356eb78a5305d3178af2cb90c38a8ce8ce9990a7cf6ff06144") @@ -4099,11 +5020,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("2f125a927c3af52ef89af11857df988a042e26ce095129701b915e75b2ec6bff") @@ -4113,11 +5036,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("b9989411bed71ba4867538c991f20b55f549dd9131905733f0df9f3fde81ad1d") @@ -4127,11 +5052,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("a60b589176879bdd465659660b87e954f969bed072c03c578ec828d6134f4ae1") @@ -4141,11 +5068,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("bb7f2a5143010fa482c5b442cced85516696cfc416ca92c903ef374532401a33") @@ -4155,11 +5084,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.10.3%2B20220318-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("ba593370742ed8a7bc70ce563dd6a53e30ece1f6881e3888d334c1b485b0d9d0") @@ -4169,11 +5100,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("1409acd9a506e2d1d3b65c1488db4e40d8f19d09a7df099667c87a506f71c0ef") @@ -4183,11 +5116,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("8146ad4390710ec69b316a5649912df0247d35f4a42e2aa9615bffd87b3e235a") @@ -4197,11 +5132,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("8f351a8cc348bb45c0f95b8634c8345ec6e749e483384188ad865b7428342703") @@ -4211,11 +5148,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("4fa49dab83bf82409816db431806525ce894280a509ca96c91e3efc9beed1fea") @@ -4225,11 +5164,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("9b64eca2a94f7aff9409ad70bdaa7fbbf8148692662e764401883957943620dd") @@ -4239,11 +5180,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("c4f398f6f7f9bbf0df98407ad66bc5760f3afc2cd8ba33a99cf4dcc8c90fd9ae") @@ -4253,11 +5196,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("5321f8c2c71239b1e2002d284be8ec825d4a6f95cd921e58db71f259834b7aa1") @@ -4267,11 +5212,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.10.2%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("a1d9a594cd3103baa24937ad9150c1a389544b4350e859200b3e5c036ac352bd") @@ -4281,13 +5228,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-install_only-20211017T1616.tar.gz", + 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 }, ManagedPythonDownload { @@ -4295,13 +5244,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-apple-darwin-install_only-20211017T1616.tar.gz", + 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 }, ManagedPythonDownload { @@ -4309,11 +5260,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-unknown-linux-gnu-lto-20211017T1616.tar.zst", sha256: None @@ -4323,11 +5276,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-i686-unknown-linux-gnu-pgo%2Blto-20211017T1616.tar.zst", sha256: None @@ -4337,13 +5292,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-unknown-linux-gnu-install_only-20211017T1616.tar.gz", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-unknown-linux-gnu-pgo%2Blto-20211017T1616.tar.zst", sha256: None }, ManagedPythonDownload { @@ -4351,11 +5308,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -4365,11 +5324,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -4379,13 +5340,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.10.0-x86_64-pc-windows-msvc-shared-install_only-20211017T1616.tar.gz", + 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 }, ManagedPythonDownload { @@ -4393,165 +5356,189 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("02c376d7a931c7a4575158e8908978ec7686cd11ba93a0df1770858974f53822") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("41e9bb2d45e1a0467e534dafc6691b3d3c2b79fd9a564562f4c0c41eb343d30a") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", - sha256: Some("dddb504549d27beb7ee39e88a9a9db72ac890df987d890edc5fa7310179ebe9f") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + sha256: Some("440f4ebc651e707ed24d5dc68d3b0b2197e7fb369bb77685b1b539dbf30ab1e5") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("4105f5324b6920af5ebc541f4f29dedd60753ef655c343b77d6835cdb5749d99") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("3742c9d6563527a003b12ac689c07e6965911ff89fd9cbbd3c17ac7bfb037d4a") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - sha256: Some("7210124060f1958ae4cebee229f311bc7de0081d89ea8d23198ade8e8e7b5b43") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + sha256: Some("33f89a84b170bbed966f3028b84f5c39b3c6e30d615585107d69c9ed9fe49564") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - sha256: Some("0ceefbdf74ba55b7d75371b4a0cfec4e0a9dd72693f63236869e288e3bf920b1") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + sha256: Some("73ecdd7318b44c88cc5877d039111067723510e922852fd207ac05b03a11863c") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("5700aed0f1c5eec375cc1d356bdd1bcf5844bf58e7b666cc7318c32a61f98953") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("556dd3e80ba644dfb8ca5a8a68681f243717d8ef4a517e486a49e1f6da46278c") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("88dfa9a5d9ded872716a8bccc7092bbd8a32519c1eaa17799fea45ddc3da19dd") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("9eb2296c68484602bf9a27659ca91f6c073c8b1c97c2791bb1b0191aa8b9e45a") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - sha256: Some("390f555ff4d5f9df83b923f6da4cd71eb88f4400d87f513aaef78b00dd29eade") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + sha256: Some("44d9d016f9820f39e5bb542782557d46876b69d23d0a204eb2f367739da623e0") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - sha256: Some("0c224f88780706cea5eefe96e3853da8d358ebb1a5f33575097136a7891daa3f") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + sha256: Some("332ce515daa15173f73d0ecebc988fadfac5583af8355d8895b3eb8086dac813") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("ef91a17d8f81b3b744be5bfbbd1531963fcfc4798832aae2ac1f8d093354bfee") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-i686-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("4d331f59031e02c857f4afbcfc933de3c68c8fb47ce919103147d760a0d7165f") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.9.20%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - sha256: Some("b7de813b6454e83c0821461b938954b9721f08f0040ed5d08aeeb487fa4c50e4") + url: "https://github.com/indygreg/python-build-standalone/releases/download/20241016/cpython-3.9.20%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + sha256: Some("ce3779065ab824333e8d6d0a3d055d4073cdcc9a6e60abe24929023369f91512") }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("451582f8a6a8c15ef35a327afcdbf8d03b1ebba7192e90d850d092dac91f91c6") @@ -4561,11 +5548,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("ebaf4336d0cbff4466c994d5bcaa92a38c91d06694d0cd675ec663259d5f37c7") @@ -4575,11 +5564,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("23d08f7f0bf151c2ea54b2c9c143bc710faf166ff74225b0f967fab1e2d7a151") @@ -4589,11 +5580,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabi), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", sha256: Some("cebb879d47874f3f943a4334a8fcd8baa3cd7ef4be8cae6b4c8ae980d981a28d") @@ -4603,11 +5596,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnueabihf), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", sha256: Some("c32d3227c44919349172c27b35275bad379f1679f729fbd4f336625903171a1a") @@ -4617,11 +5612,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("3cc60442d5694db1abe2a0c6e73459ebb6e7ba7fc7b0a986f3699d463a8f9557") @@ -4631,11 +5628,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("b41f834311532ee9dcf76cad3cdeda285d0e283de2182ce9870c37c40970cdd3") @@ -4645,11 +5644,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("3b7d574b6bbf8303789a1d26b96a81dcca907381441ce15818c784e18d1db299") @@ -4659,11 +5660,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("5c6605b1cfa6a952420f2267d10bed9ae20a02858a769b7275d8805f6b9fe40b") @@ -4673,11 +5676,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("1c78c6dd763e6d583c3c3f917544bc4446d0e9fbe2b6e206042fa801ff9fb9ab") @@ -4687,11 +5692,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.9.19%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("426da4d31e665b77dacf15cd89494a995ed634a9b97324bbef9cf36fcda4c8a9") @@ -4701,11 +5708,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("2548f911a6e316575c303ba42bb51540dc9b47a9f76a06a2a37460d93b177aa2") @@ -4715,11 +5724,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("171d8b472fce0295be0e28bb702c43d5a2a39feccb3e72efe620ac3843c3e402") @@ -4729,11 +5740,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("e5bc5196baa603d635ee6b0cd141e359752ad3e8ea76127eb9141a3155c51200") @@ -4743,11 +5756,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.9.18%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("10c422080317886057e968010495037ba65731ab7653bcaeabadf67a6fa5e99e") @@ -4757,11 +5772,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("d6b18df7a25fe034fd5ce4e64216df2cc78b2d4d908d2a1c94058ae700d73d22") @@ -4771,11 +5788,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("15d059507c7e900e9665f31e8d903e5a24a68ceed24f9a1c5ac06ab42a354f3f") @@ -4785,11 +5804,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("0e5663025121186bd17d331538a44f48b41baff247891d014f3f962cbe2716b4") @@ -4799,11 +5820,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("cb47455810ae63d98501b3bb4fcdfdb9924633fb2e86e62d77e523a3bdee44ba") @@ -4813,11 +5836,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("904ff5d2f6402640e2b7e2b12075af0bd75b3e8685cc5248fd2a3cda3105d2a8") @@ -4827,11 +5852,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("a9bdbd728ed4c353a4157ecf74386117fb2a2769a9353f491c528371cfe7f6cd") @@ -4841,11 +5868,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("73dbe2d702210b566221da9265acc274ba15275c5d0d1fa327f44ad86cde9aa1") @@ -4855,11 +5884,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("dfe1bea92c94b9cb779288b0b06e39157c5ff7e465cdd24032ac147c2af485c0") @@ -4869,11 +5900,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("b77012ddaf7e0673e4aa4b1c5085275a06eee2d66f33442b5c54a12b62b96cbe") @@ -4883,11 +5916,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("aed29a64c835444c2f1aff83c55b14123114d74c54d96493a0eabfdd8c6d012c") @@ -4897,11 +5932,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c591a28d943dce5cf9833e916125fdfbeb3120270c4866ee214493ccb5b83c3c") @@ -4911,11 +5948,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-s390x-unknown-linux-gnu-install_only.tar.gz", sha256: Some("01454d7cc7c9c2fccde42ba868c4f372eaaafa48049d49dd94c9cf2875f497e6") @@ -4925,11 +5964,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("26c4a712b4b8e11ed5c027db5654eb12927c02da4857b777afb98f7a930ce637") @@ -4939,11 +5980,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("194316e9cc7add1dd12be3e3eea2908fd4d623799edd7df69e360c6a446b750d") @@ -4953,11 +5996,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("09f9d4bc66be5e0df2dfd1dc4742923e46c271f8f085178696c77073477aa0c1") @@ -4967,11 +6012,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.9.17%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("9b9a1e21eff29dcf043cea38180cf8ca3604b90117d00062a7b31605d4157714") @@ -4981,11 +6028,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("c1de1d854717a6245f45262ef1bb17b09e2c587590e7e3f406593c143ff875bd") @@ -4995,11 +6044,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("3abc4d5fbbc80f5f848f280927ac5d13de8dc03aabb6ae65d8247cbb68e6f6bf") @@ -5009,11 +6060,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("f629b75ebfcafe9ceee2e796b7e4df5cf8dbd14f3c021afca078d159ab797acf") @@ -5023,11 +6076,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("ab0a14b3ae72bf48b94820e096e86b3cf3e05729862f768e109aa8318016c4f2") @@ -5037,11 +6092,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Powerpc64le), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz", sha256: Some("ff3ac35c58f67839aff9b5185a976abd3d1abbe61af02089f7105e876c1fe284") @@ -5051,11 +6108,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("2b6e146234a4ef2a8946081fc3fbfffe0765b80b690425a49ebe40b47c33445b") @@ -5065,11 +6124,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("5d9b13e8d5ee7a26fd0cf6e6d7e5a1ea90ddddd1f30ed2400bda60506f7dcea3") @@ -5079,11 +6140,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("219532ffa49af88e3b90e9135cf3b6e1fa11cf165b03098fb9776a07af8ca6d0") @@ -5093,11 +6156,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("cdabb47204e96ce7ea31fbd0b5ed586114dd7d8f8eddf60a509a7f70b48a1c5e") @@ -5107,11 +6172,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("64dc7e1013481c9864152c3dd806c41144c79d5e9cd3140e185c6a5060bdc9ab") @@ -5121,11 +6188,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("f2bcade6fc976c472f18f2b3204d67202d43ae55cf6f9e670f95e488f780da08") @@ -5135,11 +6204,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("52a8c0a67fb919f80962d992da1bddb511cdf92faf382701ce7673e10a8ff98f") @@ -5149,11 +6220,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("bf32a86c220e4d1690bb92b67653f20b8325808accd81bff03b5c30ae74e6444") @@ -5163,11 +6236,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("cdc3a4cfddcd63b6cebdd75b14970e02d8ef0ac5be4d350e57ab5df56c19e85e") @@ -5177,11 +6252,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("81b1c76ac789521fcececdcdc643f6de6fc282083b1a36a9973d835fc8a39391") @@ -5191,11 +6268,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("0b81089247f258f244e9792daaa03675da6f58597daa6913e82f2679862238dd") @@ -5205,11 +6284,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.9.15%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("022daacab215679b87f0d200d08b9068a721605fa4721ebeda38220fc641ccf6") @@ -5219,11 +6300,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("e38df7f230979ce6c53a5bafb3a81287838e5f3892c40cd1b98a0c961c444713") @@ -5233,11 +6316,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("b7d3a1f4b57e9350571ccee49c82f503133de0d113a2dbaebc8ccf108fb3fe1b") @@ -5247,11 +6332,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("fe538201559ca37f44cd5f66c42a65fe7272cb4f1f63edd698b6f306771db1e9") @@ -5261,11 +6348,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("3af1c255110c2f42ed0b7957502c92edf8b5c5e6fc5f699a2475bf8a560325c0") @@ -5275,11 +6364,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("e63d0c00a499e0202ba7a0f53ce69fca6d30237af39af9bc3c76bce6c7bf14d7") @@ -5289,11 +6380,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("ceb26ef5f5a9b7b47fa95225fffce9c8ef0c9c1fbeca69fbda236a0c10de7ad8") @@ -5303,11 +6396,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("f3526e8416be86ff9091750ebc7388d6726acf32cc5ab0e6a60c67c6aacb2569") @@ -5317,11 +6412,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.9.14%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("f111c3c129f4a5a171d25350ce58dad4c7e58fbe664e9b4f7c275345c9fe18a6") @@ -5331,11 +6428,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("d9603edc296a2dcbc59d7ada780fd12527f05c3e0b99f7545112daf11636d6e5") @@ -5345,11 +6444,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("9540a7efb7c8a54a48aff1cb9480e49588d9c0a3f934ad53f5b167338174afa3") @@ -5359,11 +6460,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("80415aac1b96255b9211f6a4c300f31e9940c7e07a23d0dec12b53aa52c0d25e") @@ -5373,11 +6476,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("efcc8fef0d498afe576ab209fee001fda3b552de1a85f621f2602787aa6cf3d4") @@ -5387,11 +6492,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("ce1cfca2715e7e646dd618a8cb9baff93000e345ccc979b801fc6ccde7ce97df") @@ -5401,11 +6508,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("766ed7e805e8c7abc4e50f1c94814575e7978ed7bd1f9e9ccec82d66b47b567f") @@ -5415,11 +6524,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("90e3879382f06fea3ba6d477f0c2a434a1e14cd83d174e1c7b87e2f22bc2e748") @@ -5429,11 +6540,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.9.13%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("b538127025a467c64b3351babca2e4d2ea7bdfb7867d5febb3529c34456cdcd4") @@ -5443,11 +6556,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("8dee06c07cc6429df34b6abe091a4684a86f7cec76f5d1ccc1c3ce2bd11168df") @@ -5457,11 +6572,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("2453ba7f76b3df3310353b48c881d6cff622ba06e30d2b6ae91588b2bc9e481a") @@ -5471,11 +6588,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("2ee1426c181e65133e57dc55c6a685cb1fb5e63ef02d684b8a667d5c031c4203") @@ -5485,11 +6604,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("233e1a9626d9fe13baac8de3689df48401d0ad5da1c2f134ad57d8e3e878a1a5") @@ -5499,11 +6620,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("ccca12f698b3b810d79c52f007078f520d588232a36bc12ede944ec3ea417816") @@ -5513,11 +6636,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("dd0eaf7ef64008d4a51a73243f368e0311b7936b0ac18f8d1305fffb0dfb76e6") @@ -5527,11 +6652,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("8b7e440137bfa349a008641a75a2b1fd8ae22d290731778a144878a59a721c51") @@ -5541,11 +6668,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220502/cpython-3.9.12%2B20220502-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("3024147fd987d9e1b064a3d94932178ff8e0fe98cfea955704213c0762fee8df") @@ -5555,11 +6684,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("cf92a28f98c8d884df0937bf19d5f1a40caa25a6a211a237b7e9b592b2b71c2b") @@ -5569,11 +6700,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("43889d1a424c84fb155e1619f062adb6984fbde80b6043611790f22bcbeec300") @@ -5583,11 +6716,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("0e50f099409c5e651b5fddd16124af1d830d11653e786a93c28e5b8f8aa470c4") @@ -5597,11 +6732,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("75ac727631eab002bd120246197a8235145cb90687be181f7a52de6f41d44d34") @@ -5611,11 +6748,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("0429d5ceb095d5e24c292bf1a39208b88ae236a680ef8fa3e1830e3a1a7e8882") @@ -5625,11 +6764,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("ae8f55d90ae173f96e81f376daa5a9969a77531a6f7b8eacbe8ad90b41bbca1d") @@ -5639,11 +6780,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("ceac8729b285a8c8e861176dd2dadd7f8e7e26d8f64cac6c6226a14d2252cd4c") @@ -5653,11 +6796,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220318/cpython-3.9.11%2B20220318-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("0c529a511f7a03908fc126c4a8467b47e24a4d98812147e8e786cf59e86febf0") @@ -5667,11 +6812,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("ad66c2a3e7263147e046a32694de7b897a46fb0124409d29d3a93ede631c8aee") @@ -5681,11 +6828,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("fdaf594142446029e314a9beb91f1ac75af866320b50b8b968181e592550cd68") @@ -5695,11 +6844,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("12dd1f125762f47975990ec744532a1cf3db74ad60f4dfb476ca42deb7f78ca4") @@ -5709,11 +6860,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("37ba43845c3df9ba012d69121ad29ea7f21ea2f5994a155007cf1560d74ce503") @@ -5723,11 +6876,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("455089cc576bd9a58db45e919d1fc867ecdbb0208067dffc845cc9bbf0701b70") @@ -5737,11 +6892,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("30add63ec16e07ad13e19f6d7061f7e4c7b971962354f48ab3e85656ce3b393d") @@ -5751,11 +6908,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("56c0342a9af0412676e89cdf7b52ac76037031786b3f5c40942b8b82d366c96f") @@ -5765,11 +6924,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.9.10%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("c145d9d8143ce163670af124b623d7a2405143a3708b033b4d33eed355e61b24") @@ -5779,13 +6940,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-apple-darwin-install_only-20211017T1616.tar.gz", + 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 }, ManagedPythonDownload { @@ -5793,13 +6956,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-apple-darwin-install_only-20211017T1616.tar.gz", + 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 }, ManagedPythonDownload { @@ -5807,11 +6972,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-aarch64-unknown-linux-gnu-lto-20211017T1616.tar.zst", sha256: None @@ -5821,11 +6988,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-i686-unknown-linux-gnu-pgo%2Blto-20211017T1616.tar.zst", sha256: None @@ -5835,13 +7004,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-unknown-linux-gnu-install_only-20211017T1616.tar.gz", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-unknown-linux-gnu-pgo%2Blto-20211017T1616.tar.zst", sha256: None }, ManagedPythonDownload { @@ -5849,11 +7020,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -5863,11 +7036,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -5877,13 +7052,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20211017/cpython-3.9.7-x86_64-pc-windows-msvc-shared-install_only-20211017T1616.tar.gz", + 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 }, ManagedPythonDownload { @@ -5891,13 +7068,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-apple-darwin-install_only-20210724T1424.tar.gz", + 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 }, ManagedPythonDownload { @@ -5905,13 +7084,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-apple-darwin-install_only-20210724T1424.tar.gz", + 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 }, ManagedPythonDownload { @@ -5919,11 +7100,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-aarch64-unknown-linux-gnu-lto-20210724T1424.tar.zst", sha256: None @@ -5933,11 +7116,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-i686-unknown-linux-gnu-pgo%2Blto-20210724T1424.tar.zst", sha256: None @@ -5947,13 +7132,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-unknown-linux-gnu-install_only-20210724T1424.tar.gz", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-unknown-linux-gnu-pgo%2Blto-20210724T1424.tar.zst", sha256: None }, ManagedPythonDownload { @@ -5961,11 +7148,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -5975,11 +7164,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -5989,13 +7180,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.9.6-x86_64-pc-windows-msvc-shared-install_only-20210724T1424.tar.gz", + 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 }, ManagedPythonDownload { @@ -6003,11 +7196,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6017,11 +7212,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6031,11 +7228,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-i686-unknown-linux-gnu-pgo%2Blto-20210506T0943.tar.zst", sha256: None @@ -6045,11 +7244,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.9.5-x86_64-unknown-linux-gnu-pgo%2Blto-20210506T0943.tar.zst", sha256: None @@ -6059,11 +7260,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -6073,11 +7276,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6087,11 +7292,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6101,11 +7308,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6115,11 +7324,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6129,11 +7340,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-i686-unknown-linux-gnu-pgo%2Blto-20210414T1515.tar.zst", sha256: None @@ -6143,11 +7356,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.9.4-x86_64-unknown-linux-gnu-pgo%2Blto-20210414T1515.tar.zst", sha256: None @@ -6157,11 +7372,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -6171,11 +7388,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6185,11 +7404,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 4, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6199,11 +7420,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6213,11 +7436,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6227,11 +7452,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210414/cpython-3.9.3-x86_64-unknown-linux-gnu-pgo%2Blto-20210413T2055.tar.zst", sha256: None @@ -6241,11 +7468,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -6255,11 +7484,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6269,11 +7500,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6283,11 +7516,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6297,11 +7532,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6311,11 +7548,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-i686-unknown-linux-gnu-pgo%2Blto-20210327T1202.tar.zst", sha256: None @@ -6325,11 +7564,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.9.2-x86_64-unknown-linux-gnu-pgo%2Blto-20210327T1202.tar.zst", sha256: None @@ -6339,11 +7580,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -6353,11 +7596,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6367,11 +7612,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6381,11 +7628,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6395,11 +7644,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-unknown-linux-gnu-pgo-20210103T1125.tar.zst", sha256: None @@ -6409,13 +7660,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-unknown-linux-musl-debug-20210103T1125.tar.zst", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.9.1-x86_64-unknown-linux-musl-noopt-20210103T1125.tar.zst", sha256: None }, ManagedPythonDownload { @@ -6423,11 +7676,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6437,11 +7692,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 1, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6451,11 +7708,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6465,11 +7724,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-unknown-linux-gnu-pgo-20201020T0627.tar.zst", sha256: None @@ -6479,13 +7740,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-unknown-linux-musl-debug-20201020T0627.tar.zst", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.9.0-x86_64-unknown-linux-musl-noopt-20201020T0627.tar.zst", sha256: None }, ManagedPythonDownload { @@ -6493,11 +7756,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6507,11 +7772,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 0, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -6521,11 +7788,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("30ba44af64e599bde7307908393374bdcd99e185bf9b3c9de3f697f3fbe6bf8f") @@ -6535,11 +7804,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("375b6eead6c852cabbf3ccfd43dc4f6dd4c36381bf74c9a7910acb839fd5c57f") @@ -6549,11 +7820,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("75a187ebfab81096e3f3d91d70c1349e64defbdfb0e8a067cb5233d017655e31") @@ -6563,11 +7836,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("a3a75094545912d4e9413673441b3f0d2e58ce9b264477f910800148801ccf11") @@ -6577,11 +7852,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("fcddfd3f1090833e1f3106be021809630008b53026bc96dcaab2986625db27fa") @@ -6591,11 +7868,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("d829105aaf53a1cadf8738e040c6211bc9bef2c6e4757b972954f0f322d57e7d") @@ -6605,11 +7884,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 20, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("ec2f723dcfbf09581578a716c05cc67823a43d77111e6dd9e0d1557ccc6dcbf3") @@ -6619,11 +7900,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("6a15ee2b507aed4d5b15fd1b66fc570aa49183f15aa6c412eccd065446f17d8e") @@ -6633,11 +7916,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", sha256: Some("1a24263b039c1172bd42d74a5694492f3e3dbe4d3e52a1e7cc2856fee7dbee4a") @@ -6647,11 +7932,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("202211923850303f521146ee1831642aaf357ffeeadbe13a0a91884317227528") @@ -6661,11 +7948,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", sha256: Some("0f1579dbb01c98af7a12fef4c9aa8a99d45b91393f64431f5de712f892bc5c0b") @@ -6675,11 +7964,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", sha256: Some("6ee6c7469c9d2c7078beb95a9a3a261c42502e0b1603722a0689bdb2e789060c") @@ -6689,11 +7980,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-i686-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("73bf0135330b96c48ca79ccd6d2f3287a7466573a5fc1b62d982bcdb1d5f0ab3") @@ -6703,11 +7996,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240814/cpython-3.8.19%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", sha256: Some("89d238b125cd7546b7d0cbd7f484a438d2c2f239c15c9b38ec3c62b1f343a6ca") @@ -6717,11 +8012,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("4d493a1792bf211f37f98404cc1468f09bd781adc2602dea0df82ad264c11abc") @@ -6731,11 +8028,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("7d2cd8d289d5e3cdd0a8c06c028c7c621d3d00ce44b7e2f08c1724ae0471c626") @@ -6745,11 +8044,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("6588c9eed93833d9483d01fe40ac8935f691a1af8e583d404ec7666631b52487") @@ -6759,11 +8060,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("5ae36825492372554c02708bdd26b8dcd57e3dbf34b3d6d599ad91d93540b2b7") @@ -6773,11 +8076,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("e591d3925f88f78a5dffb765fd10b9dab6e497d35cf58169da83eab521c86a37") @@ -6787,11 +8092,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("c24f9c9e8638cff0ce6aa808a57cc5f22009bc33e3bcf410a726b79d7c5545fe") @@ -6801,11 +8108,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("dba923ee5df8f99db04f599e826be92880746c02247c8d8e4d955d4bc711af11") @@ -6815,11 +8124,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("c6f7a130d0044a78e39648f4dae56dcff5a41eba91888a99f6e560507162e6a1") @@ -6829,11 +8140,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("155b06821607bae1a58ecc60a7d036b358c766f19e493b8876190765c883a5c2") @@ -6843,11 +8156,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("9f6d585091fe26906ff1dbb80437a3fe37a1e3db34d6ecc0098f3d6a78356682") @@ -6857,11 +8172,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("e580fdd923bbae612334559dc58bd5fd13cce53b769294d63bc88e7c6662f7d9") @@ -6871,11 +8188,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("8d3e1826c0bb7821ec63288038644808a2d45553245af106c685ef5892fabcd8") @@ -6885,11 +8204,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("322b7837cfd8282c62ae3d2f0e98f0843cbe287e4b8c4852b786123f2e13b307") @@ -6899,11 +8220,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("cb6af626ba811044e9c5ee09140a6920565d2b1b237a11886b96354a9fcc242e") @@ -6913,11 +8236,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("6428e1b4e0b4482d390828de7d4c82815257443416cb786abe10cb2466ca68cd") @@ -6927,11 +8252,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("7e484eb6de40d6f6bdfd5099eaa9647f65e45fb6d846ccfc56b1cb1e38b5ab02") @@ -6941,11 +8268,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("28506e509646c11cb2f57a7203bd1b08b6e8e5b159ae308bd5bb93b0d334bdaf") @@ -6955,11 +8284,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("9c6615931fd1045bf9f2148aa7dd9ce1ece8575ed68a5483a0b615322a43d54c") @@ -6969,11 +8300,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("1260fd6af34104bbd57489175e6f7bfea76d4bd06a242a0f8e20e390e870b227") @@ -6983,11 +8316,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("b1f1502c3a13b899724dbd32bd77a973fa9733b932c5700d747fe33d5de9ac4f") @@ -6997,11 +8332,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("840aefa3b03b66b6561360735dc0ac4e0a36a3ebb4d1f85d92f5b5f6638953cc") @@ -7011,11 +8348,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("77466f93ef5b030cf13d0446067089b0ce0d415cc6d1702655bdbb12a8c18c97") @@ -7025,11 +8364,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20230726/cpython-3.8.16%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("120b3312fa79bac2ace45641171c2bc590c4e4462d7ad124d64597e124a36ae7") @@ -7039,11 +8380,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("1e0a92d1a4f5e6d4a99f86b1cbf9773d703fe7fd032590f3e9c285c7a5eeb00a") @@ -7053,11 +8396,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("70b57f28c2b5e1e3dd89f0d30edd5bc414e8b20195766cf328e1b26bed7890e1") @@ -7067,11 +8412,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("886ab33ced13c84bf59ce8ff79eba6448365bfcafea1bf415bd1d75e21b690aa") @@ -7081,11 +8428,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("3bc1f49147913d93cea9cbb753fbaae90b86f1ee979f975c4712a35f02cbd86b") @@ -7095,11 +8444,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("e47edfb2ceaf43fc699e20c179ec428b6f3e497cf8e2dcd8e9c936d4b96b1e56") @@ -7109,11 +8460,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("f767d0438eca5b18c1267c5121055a5808a1412ea7668ef17da3dc9bdd24a55f") @@ -7123,11 +8476,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("318c059324b84b5d7685bcd0874698799d9e3689b51dbcf596e7a47a39a3d49a") @@ -7137,11 +8492,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221106/cpython-3.8.15%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("2fdc3fa1c95f982179bbbaedae2b328197658638799b6dcb63f9f494b0de59e2") @@ -7151,11 +8508,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("6c17f6dcda59de5d8eee922ef7eede403a540dae05423ef2c2a042d8d4f22467") @@ -7165,11 +8524,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("3ed4db8d0308c584196d97c629058ea69bbd8b7f9a034cf8c2c701ebb286c091") @@ -7179,11 +8540,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("c45e42deee43e3ebc4ca5b019c37d8ae25fb5b5f1ba5f602098a81b99d2bc804") @@ -7193,11 +8556,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("d01d813939ad549ca253c52e5b8361b4490cc5c8cbda00ab6e0c524565153e2b") @@ -7207,11 +8572,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("4eb53bce831bf52682067579c09ccaccb6524dd44bd4b8047454c69b4817f4f0") @@ -7221,11 +8588,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("72c08b1c1d8cc14cb8d22eab18b463bb514ea160472fdc7400bd69ae375cf9c4") @@ -7235,11 +8604,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("a0730f3a9e60581f02bdb852953fbb52cf98e8431259fa39cb668a060bd002a0") @@ -7249,11 +8620,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20221002/cpython-3.8.14%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("1af39953b4c8324ed0608e316bc763006f27e76643155d92eae18e4db6fc162f") @@ -7263,11 +8636,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("ae4131253d890b013171cb5f7b03cadc585ae263719506f7b7e063a7cf6fde76") @@ -7277,11 +8652,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("cd6e7c0a27daf7df00f6882eaba01490dd963f698e99aeee9706877333e0df69") @@ -7291,11 +8668,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("8dc7814bf3425bbf78c6e6e5a6529ded6ae463fa6a4b79c025b343bae4fd955a") @@ -7305,11 +8684,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("9485599ad9053dfba08c91854717272e95b7c81e0d099d9c51a46fc5a095ccb4") @@ -7319,11 +8700,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("fb566629ccb5f76ef56d275a3f8017d683f1c20c5beb5d5f38b155ed11e16187") @@ -7333,11 +8716,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("2c90a0d048caf146d4c33560d6eead1428a225219018d364b1af77f23c492984") @@ -7347,11 +8732,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("a50668d4c5fbcb374d3ca93ee18db910bc3b462693db073669f31e6da993abf9") @@ -7361,11 +8748,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220802/cpython-3.8.13%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("f20643f1b3e263a56287319aea5c3888530c09ad9de3a5629b1a5d207807e6b9") @@ -7375,11 +8764,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-aarch64-apple-darwin-install_only.tar.gz", sha256: Some("f9a3cbb81e0463d6615125964762d133387d561b226a30199f5b039b20f1d944") @@ -7389,11 +8780,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-apple-darwin-install_only.tar.gz", sha256: Some("f323fbc558035c13a85ce2267d0fad9e89282268ecb810e364fff1d0a079d525") @@ -7403,11 +8796,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-i686-unknown-linux-gnu-install_only.tar.gz", sha256: Some("fcb2033f01a2b10a51be68c9a1b4c7d7759b582f58a503371fe67ab59987b418") @@ -7417,11 +8812,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", sha256: Some("5be9c6d61e238b90dfd94755051c0d3a2d8023ebffdb4b0fa4e8fedd09a6cab6") @@ -7431,11 +8828,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-unknown-linux-musl-install_only.tar.gz", sha256: Some("27faf8aa62de2cd4e59b75a6edce4cab549eba81f0f9cc21df0e370a8a2f3a25") @@ -7445,11 +8844,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-i686-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("aaa75b9115af73dc3daf7db050ed4f60fd67d2a23ebab30670f18fb8cfa71f33") @@ -7459,11 +8860,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20220227/cpython-3.8.12%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", sha256: Some("4658e08a00d60b1e01559b74d58ff4dd04da6df935d55f6268a15d6d0a679d74") @@ -7473,11 +8876,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7487,11 +8892,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-i686-unknown-linux-gnu-pgo%2Blto-20210724T1424.tar.zst", sha256: None @@ -7501,11 +8908,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210724/cpython-3.8.11-x86_64-unknown-linux-gnu-pgo%2Blto-20210724T1424.tar.zst", sha256: None @@ -7515,11 +8924,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -7529,11 +8940,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7543,11 +8956,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 11, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7557,11 +8972,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7571,11 +8988,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-i686-unknown-linux-gnu-pgo%2Blto-20210506T0943.tar.zst", sha256: None @@ -7585,11 +9004,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210506/cpython-3.8.10-x86_64-unknown-linux-gnu-pgo%2Blto-20210506T0943.tar.zst", sha256: None @@ -7599,11 +9020,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -7613,11 +9036,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7627,11 +9052,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7641,11 +9068,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7655,11 +9084,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-i686-unknown-linux-gnu-pgo%2Blto-20210414T1515.tar.zst", sha256: None @@ -7669,11 +9100,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210415/cpython-3.8.9-x86_64-unknown-linux-gnu-pgo%2Blto-20210414T1515.tar.zst", sha256: None @@ -7683,11 +9116,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -7697,11 +9132,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7711,11 +9148,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7725,11 +9164,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7739,11 +9180,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-i686-unknown-linux-gnu-pgo%2Blto-20210327T1202.tar.zst", sha256: None @@ -7753,11 +9196,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210327/cpython-3.8.8-x86_64-unknown-linux-gnu-pgo%2Blto-20210327T1202.tar.zst", sha256: None @@ -7767,11 +9212,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, 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 @@ -7781,11 +9228,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7795,11 +9244,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 8, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7809,11 +9260,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7823,11 +9276,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-unknown-linux-gnu-pgo-20210103T1125.tar.zst", sha256: None @@ -7837,13 +9292,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-unknown-linux-musl-debug-20210103T1125.tar.zst", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20210103/cpython-3.8.7-x86_64-unknown-linux-musl-noopt-20210103T1125.tar.zst", sha256: None }, ManagedPythonDownload { @@ -7851,11 +9308,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7865,11 +9324,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7879,11 +9340,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7893,11 +9356,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-unknown-linux-gnu-pgo-20201020T0627.tar.zst", sha256: None @@ -7907,13 +9372,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-unknown-linux-musl-debug-20201020T0627.tar.zst", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20201020/cpython-3.8.6-x86_64-unknown-linux-musl-noopt-20201020T0627.tar.zst", sha256: None }, ManagedPythonDownload { @@ -7921,11 +9388,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7935,11 +9404,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7949,11 +9420,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -7963,11 +9436,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.8.5-x86_64-unknown-linux-gnu-pgo-20200823T0036.tar.zst", sha256: None @@ -7977,13 +9452,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.8.5-x86_64-unknown-linux-musl-debug-20200823T0036.tar.zst", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.8.5-x86_64-unknown-linux-musl-noopt-20200823T0036.tar.zst", sha256: None }, ManagedPythonDownload { @@ -7991,11 +9468,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8005,11 +9484,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 5, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8019,11 +9500,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8033,11 +9516,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-unknown-linux-gnu-pgo-20200518T0040.tar.zst", sha256: None @@ -8047,13 +9532,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-unknown-linux-musl-debug-20200518T0040.tar.zst", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.8.3-x86_64-unknown-linux-musl-noopt-20200518T0040.tar.zst", sha256: None }, ManagedPythonDownload { @@ -8061,11 +9548,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8075,11 +9564,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 3, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8089,11 +9580,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8103,11 +9596,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-unknown-linux-gnu-pgo-20200418T2243.tar.zst", sha256: None @@ -8117,11 +9612,29 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, + implementation: LenientImplementationName::Known(ImplementationName::CPython), + arch: Arch(target_lexicon::Architecture::X86_64), + os: Os(target_lexicon::OperatingSystem::Linux), + libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + + }, + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200418/cpython-3.8.2-x86_64-unknown-linux-musl-noopt-20200418T2309.tar.zst", + sha256: None + }, + ManagedPythonDownload { + key: PythonInstallationKey { + major: 3, + minor: 8, + patch: 2, + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8131,11 +9644,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 2, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8145,11 +9660,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8159,11 +9676,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-gnu-pgo-20200823T0036.tar.zst", sha256: None @@ -8173,13 +9692,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-musl-debug-20200823T0036.tar.zst", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-musl-noopt-20200823T0036.tar.zst", sha256: None }, ManagedPythonDownload { @@ -8187,11 +9708,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8201,11 +9724,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8215,11 +9740,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8229,11 +9756,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-unknown-linux-gnu-pgo-20200518T0040.tar.zst", sha256: None @@ -8243,13 +9772,15 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Musl), + variant: PythonVariant::Default + }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-unknown-linux-musl-debug-20200518T0040.tar.zst", + url: "https://github.com/indygreg/python-build-standalone/releases/download/20200517/cpython-3.7.7-x86_64-unknown-linux-musl-noopt-20200518T0040.tar.zst", sha256: None }, ManagedPythonDownload { @@ -8257,11 +9788,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8271,11 +9804,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 7, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, 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 @@ -8285,53 +9820,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 6, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Darwin), - libc: Libc::None, - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-macos-20200216T2344.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 6, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Gnu), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-linux64-20200216T2303.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 6, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Musl), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20200217/cpython-3.7.6-linux64-musl-20200218T0557.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-x86-shared-pgo-20200217T0110.tar.zst", sha256: None @@ -8341,249 +9836,29 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 6, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::CPython), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://github.com/indygreg/python-build-standalone/releases/download/20200216/cpython-3.7.6-windows-amd64-shared-pgo-20200217T0022.tar.zst", sha256: None }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 5, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Darwin), - libc: Libc::None, - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-macos-20191026T0535.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 5, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Gnu), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-linux64-20191025T0506.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 5, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Musl), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20191025/cpython-3.7.5-linux64-musl-20191026T0603.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 5, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), - os: Os(target_lexicon::OperatingSystem::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 - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 5, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::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 - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 4, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Darwin), - libc: Libc::None, - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-macos-20190817T0220.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 4, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Gnu), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-linux64-20190817T0224.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 4, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Musl), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20190816/cpython-3.7.4-linux64-musl-20190817T0227.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 4, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), - os: Os(target_lexicon::OperatingSystem::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 - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 4, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::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 - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 3, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Darwin), - libc: Libc::None, - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-macos-20190618T0523.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 3, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Gnu), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-linux64-20190618T0324.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 3, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Musl), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20190617/cpython-3.7.3-linux64-musl-20190618T0400.tar.zst", - sha256: None - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 3, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), - os: Os(target_lexicon::OperatingSystem::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 - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 3, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::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 - }, - ManagedPythonDownload { - key: PythonInstallationKey { - major: 3, - minor: 7, - patch: 1, - prerelease: Cow::Borrowed(""), - implementation: LenientImplementationName::Known(ImplementationName::CPython), - arch: Arch(target_lexicon::Architecture::X86_64), - os: Os(target_lexicon::OperatingSystem::Linux), - libc: Libc::Some(target_lexicon::Environment::Gnu), - }, - url: "https://github.com/indygreg/python-build-standalone/releases/download/20181218/cpython-3.7.1-linux64-20181218T1905.tar.zst", - sha256: None - }, ManagedPythonDownload { key: PythonInstallationKey { major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_arm64.tar.bz2", sha256: Some("a050e25e8d686853dd5afc363e55625165825dacfb55f8753d8225ebe417cfd2") @@ -8593,11 +9868,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_x86_64.tar.bz2", sha256: Some("6c2c5f2300d7564e711421b4968abd63243cb96f76e363975dd648ebf4a362ee") @@ -8607,11 +9884,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.17-aarch64.tar.bz2", sha256: Some("53b6e5907df869c49e4eae7aca09fba16d150741097efb245892c1477d2395f2") @@ -8621,11 +9900,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.17-linux32.tar.bz2", sha256: Some("e534110e1047da37c1d586c392f74de3424f871d906a2083de6d41f2a8cc9164") @@ -8635,11 +9916,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.16-s390x.tar.bz2", sha256: Some("af97efe498a209ba18c7bc7d084164a9907fb3736588b6864955177e19d5216a") @@ -8649,11 +9932,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.17-linux64.tar.bz2", sha256: Some("fdcdb9b24f1a7726003586503fdeb264fd68fc37fbfcea022dcfe825a7fee18b") @@ -8663,11 +9948,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 14, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.17-win64.zip", sha256: Some("cab794a03ddda26238c72942ea6f225612e0dc17c76cac6652da83a95024e6e8") @@ -8677,11 +9964,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.15-macos_arm64.tar.bz2", sha256: Some("d927c5105ea7880f7596fe459183e35cc17c853ef5105678b2ad62a8d000a548") @@ -8691,11 +9980,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.15-macos_x86_64.tar.bz2", sha256: Some("559b61ba7e7c5a5c23cef5370f1fab47ccdb939ac5d2b42b4bef091abe3f6964") @@ -8705,11 +9996,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.15-aarch64.tar.bz2", sha256: Some("52146fccaf64e87e71d178dda8de63c01577ec3923073dc69e1519622bcacb74") @@ -8719,11 +10012,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.15-linux32.tar.bz2", sha256: Some("75dd58c9abd8b9d78220373148355bc3119febcf27a2c781d64ad85e7232c4aa") @@ -8733,11 +10028,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.15-s390x.tar.bz2", sha256: Some("209e57596381e13c9914d1332f359dc4b78de06576739747eb797bdbf85062b8") @@ -8747,11 +10044,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.15-linux64.tar.bz2", sha256: Some("33c584e9a70a71afd0cb7dd8ba9996720b911b3b8ed0156aea298d4487ad22c3") @@ -8761,11 +10060,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.15-win64.zip", sha256: Some("b378b3ab1c3719aee0c3e5519e7bff93ff67b2d8aa987fe4f088b54382db676c") @@ -8775,11 +10076,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2", sha256: Some("45671b1e9437f95ccd790af10dbeb57733cca1ed9661463b727d3c4f5caa7ba0") @@ -8789,11 +10092,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2", sha256: Some("dbc15d8570560d5f79366883c24bc42231a92855ac19a0f28cb0adeb11242666") @@ -8803,11 +10108,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", sha256: Some("26208b5a134d9860a08f74cce60960005758e82dc5f0e3566a48ed863a1f16a1") @@ -8817,11 +10124,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux32.tar.bz2", sha256: Some("811667825ae58ada4b7c3d8bc1b5055b9f9d6a377e51aedfbe0727966603f60e") @@ -8831,11 +10140,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.12-s390x.tar.bz2", sha256: Some("043c13a585479428b463ab69575a088db74aadc16798d6e677d97f563585fee3") @@ -8845,11 +10156,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2", sha256: Some("6c577993160b6f5ee8cab73cd1a807affcefafe2f7441c87bd926c10505e8731") @@ -8859,11 +10172,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 10, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip", sha256: Some("8c3b1d34fb99100e230e94560410a38d450dc844effbee9ea183518e4aff595c") @@ -8873,11 +10188,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2", sha256: Some("88f824e7a2d676440d09bc90fc959ae0fd3557d7e2f14bfbbe53d41d159a47fe") @@ -8887,11 +10204,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_x86_64.tar.bz2", sha256: Some("fda015431621e7e5aa16359d114f2c45a77ed936992c1efff86302e768a6b21c") @@ -8901,11 +10220,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.16-aarch64.tar.bz2", sha256: Some("de3f2ed3581b30555ac0dd3e4df78a262ec736a36fb2e8f28259f8539b278ef4") @@ -8915,11 +10236,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.16-linux32.tar.bz2", sha256: Some("583b6d6dd4e8c07cbc04da04a7ec2bdfa6674825289c2378c5e018d5abe779ea") @@ -8929,11 +10252,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.16-s390x.tar.bz2", sha256: Some("7a56ebb27dba3110dc1ff52d8e0449cdb37fe5c2275f7faf11432e4e164833ba") @@ -8943,11 +10268,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.16-linux64.tar.bz2", sha256: Some("16f9c5b808c848516e742986e826b833cdbeda09ad8764e8704595adbe791b23") @@ -8957,11 +10284,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 19, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip", sha256: Some("06ec12a5e964dc0ad33e6f380185a4d295178dce6d6df512f508e7aee00a1323") @@ -8971,11 +10300,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.15-macos_arm64.tar.bz2", sha256: Some("300541c32125767a91b182b03d9cc4257f04971af32d747ecd4d62549d72acfd") @@ -8985,11 +10316,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.15-macos_x86_64.tar.bz2", sha256: Some("18ad7c9cb91c5e8ef9d40442b2fd1f6392ae113794c5b6b7d3a45e04f19edec6") @@ -8999,11 +10332,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.15-aarch64.tar.bz2", sha256: Some("03e35fcba290454bb0ccf7ee57fb42d1e63108d10d593776a382c0a2fe355de0") @@ -9013,11 +10348,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.15-linux32.tar.bz2", sha256: Some("c6209380977066c9e8b96e8258821c70f996004ce1bc8659ae83d4fd5a89ff5c") @@ -9027,11 +10364,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.15-s390x.tar.bz2", sha256: Some("deeb5e54c36a0fd9cfefd16e63a0d5bed4f4a43e6bbc01c23f0ed8f7f1c0aaf3") @@ -9041,11 +10380,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.15-linux64.tar.bz2", sha256: Some("f062be307200bde434817e1620cebc13f563d6ab25309442c5f4d0f0d68f0912") @@ -9055,11 +10396,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 18, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.15-win64.zip", sha256: Some("a156dad8b58570597eaaabe05663f00f80c60bc11df4a9c46d0953b6c5eb9209") @@ -9069,11 +10412,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_arm64.tar.bz2", sha256: Some("0e8a1a3468b9790c734ac698f5b00cc03fc16899ccc6ce876465fac0b83980e3") @@ -9083,11 +10428,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_x86_64.tar.bz2", sha256: Some("64f008ffa070c407e5ef46c8256b2e014de7196ea5d858385861254e7959f4eb") @@ -9097,11 +10444,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.12-aarch64.tar.bz2", sha256: Some("e9327fb9edaf2ad91935d5b8563ec5ff24193bddb175c1acaaf772c025af1824") @@ -9111,11 +10460,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.12-linux32.tar.bz2", sha256: Some("aa04370d38f451683ccc817d76c2b3e0f471dbb879e0bd618d9affbdc9cd37a4") @@ -9125,11 +10476,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.12-s390x.tar.bz2", sha256: Some("20d84658a6899bdd2ca35b00ead33a2f56cff2c40dce1af630466d27952f6d4f") @@ -9139,11 +10492,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.12-linux64.tar.bz2", sha256: Some("84c89b966fab2b58f451a482ee30ca7fec3350435bd0b9614615c61dc6da2390") @@ -9153,11 +10508,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 17, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.12-win64.zip", sha256: Some("0996054207b401aeacace1aa11bad82cfcb463838a1603c5f263626c47bbe0e6") @@ -9167,11 +10524,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_arm64.tar.bz2", sha256: Some("91ad7500f1a39531dbefa0b345a3dcff927ff9971654e8d2e9ef7c5ae311f57e") @@ -9181,11 +10540,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_x86_64.tar.bz2", sha256: Some("d33f40b207099872585afd71873575ca6ea638a27d823bc621238c5ae82542ed") @@ -9195,11 +10556,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.11-aarch64.tar.bz2", sha256: Some("09175dc652ed895d98e9ad63d216812bf3ee7e398d900a9bf9eb2906ba8302b9") @@ -9209,11 +10572,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux32.tar.bz2", sha256: Some("0099d72c2897b229057bff7e2c343624aeabdc60d6fb43ca882bff082f1ffa48") @@ -9223,11 +10588,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.11-s390x.tar.bz2", sha256: Some("e1f30f2ddbe3f446ddacd79677b958d56c07463b20171fb2abf8f9a3178b79fc") @@ -9237,11 +10604,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2", sha256: Some("d506172ca11071274175d74e9c581c3166432d0179b036470e3b9e8d20eae581") @@ -9251,11 +10620,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.11-win64.zip", sha256: Some("57faad132d42d3e7a6406fcffafffe0b4f390cf0e2966abb8090d073c6edf405") @@ -9265,11 +10636,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.10-macos_arm64.tar.bz2", sha256: Some("e2a6bec7408e6497c7de8165aa4a1b15e2416aec4a72f2578f793fb06859ccba") @@ -9279,11 +10652,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.10-macos_x86_64.tar.bz2", sha256: Some("f90c8619b41e68ec9ffd7d5e913fe02e60843da43d3735b1c1bc75bcfe638d97") @@ -9293,11 +10668,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.10-aarch64.tar.bz2", sha256: Some("657a04fd9a5a992a2f116a9e7e9132ea0c578721f59139c9fb2083775f71e514") @@ -9307,11 +10684,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.10-linux32.tar.bz2", sha256: Some("b6db59613b9a1c0c1ab87bc103f52ee95193423882dc8a848b68850b8ba59cc5") @@ -9321,11 +10700,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.10-s390x.tar.bz2", sha256: Some("ca6525a540cf0c682d1592ae35d3fbc97559a97260e4b789255cc76dde7a14f0") @@ -9335,11 +10716,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.10-linux64.tar.bz2", sha256: Some("95cf99406179460d63ddbfe1ec870f889d05f7767ce81cef14b88a3a9e127266") @@ -9349,11 +10732,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.10-win64.zip", sha256: Some("07e18b7b24c74af9730dfaab16e24b22ef94ea9a4b64cbb2c0d80610a381192a") @@ -9363,11 +10748,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.9-osx64.tar.bz2", sha256: Some("59c8852168b2b1ba1f0211ff043c678760380d2f9faf2f95042a8878554dbc25") @@ -9377,11 +10764,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.9-aarch64.tar.bz2", sha256: Some("2e1ae193d98bc51439642a7618d521ea019f45b8fb226940f7e334c548d2b4b9") @@ -9391,11 +10780,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.9-linux32.tar.bz2", sha256: Some("0de4b9501cf28524cdedcff5052deee9ea4630176a512bdc408edfa30914bae7") @@ -9405,11 +10796,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.9-s390x.tar.bz2", sha256: Some("774dca83bcb4403fb99b3d155e7bd572ef8c52b9fe87a657109f64e75ad71732") @@ -9419,11 +10812,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.9-linux64.tar.bz2", sha256: Some("46818cb3d74b96b34787548343d266e2562b531ddbaf330383ba930ff1930ed5") @@ -9433,11 +10828,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.9-win64.zip", sha256: Some("be48ab42f95c402543a7042c999c9433b17e55477c847612c8733a583ca6dff5") @@ -9447,11 +10844,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.8-osx64.tar.bz2", sha256: Some("95bd88ac8d6372cd5b7b5393de7b7d5c615a0c6e42fdb1eb67f2d2d510965aee") @@ -9461,11 +10860,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.8-aarch64-portable.tar.bz2", sha256: Some("b7282bc4484bceae5bc4cc04e05ee4faf51cb624c8fc7a69d92e5fdf0d0c96aa") @@ -9475,11 +10876,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.8-linux32.tar.bz2", sha256: Some("a0d18e4e73cc655eb02354759178b8fb161d3e53b64297d05e2fff91f7cf862d") @@ -9489,11 +10892,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.8-s390x.tar.bz2", sha256: Some("37b596bfe76707ead38ffb565629697e9b6fa24e722acc3c632b41ec624f5d95") @@ -9503,11 +10908,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.8-linux64.tar.bz2", sha256: Some("129a055032bba700cd1d0acacab3659cf6b7180e25b1b2f730e792f06d5b3010") @@ -9517,11 +10924,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 9, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.9-v7.3.8-win64.zip", sha256: Some("c1b2e4cde2dcd1208d41ef7b7df8e5c90564a521e7a5db431673da335a1ba697") @@ -9531,11 +10940,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2", sha256: Some("78cdc79ff964c4bfd13eb45a7d43a011cbe8d8b513323d204891f703fdc4fa1a") @@ -9545,11 +10956,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2", sha256: Some("194ca0b4d91ae409a9cb1a59eb7572d7affa8a451ea3daf26539aa515443433a") @@ -9559,11 +10972,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.11-aarch64.tar.bz2", sha256: Some("9a2fa0b8d92b7830aa31774a9a76129b0ff81afbd22cd5c41fbdd9119e859f55") @@ -9573,11 +10988,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux32.tar.bz2", sha256: Some("a79b31fce8f5bc1f9940b6777134189a1d3d18bda4b1c830384cda90077c9176") @@ -9587,11 +11004,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.11-s390x.tar.bz2", sha256: Some("eab7734d86d96549866f1cba67f4f9c73c989f6a802248beebc504080d4c3fcd") @@ -9601,11 +11020,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux64.tar.bz2", sha256: Some("470330e58ac105c094041aa07bb05676b06292bc61409e26f5c5593ebb2292d9") @@ -9615,11 +11036,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 16, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip", sha256: Some("0f46fb6df32941ea016f77cfd7e9b426d5ac25a2af2453414df66103941c8435") @@ -9629,11 +11052,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.10-macos_arm64.tar.bz2", sha256: Some("6cb1429371e4854b718148a509d80143f801e3abfc72fef58d88aeeee1e98f9e") @@ -9643,11 +11068,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.10-macos_x86_64.tar.bz2", sha256: Some("399eb1ce4c65f62f6a096b7c273536601b7695e3c0dc0457393a659b95b7615b") @@ -9657,11 +11084,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.10-aarch64.tar.bz2", sha256: Some("e4caa1a545f22cfee87d5b9aa6f8852347f223643ad7d2562e0b2a2f4663ad98") @@ -9671,11 +11100,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.10-linux32.tar.bz2", sha256: Some("b70ed7fdc73a74ebdc04f07439f7bad1a849aaca95e26b4a74049d0e483f071c") @@ -9685,11 +11116,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.10-s390x.tar.bz2", sha256: Some("c294f8e815158388628fe77ac5b8ad6cd93c8db1359091fa02d41cf6da4d61a1") @@ -9699,11 +11132,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.10-linux64.tar.bz2", sha256: Some("ceef6496fd4ab1c99e3ec22ce657b8f10f8bb77a32427fadfb5e1dd943806011") @@ -9713,11 +11148,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 15, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.10-win64.zip", sha256: Some("362dd624d95bd64743190ea2539b97452ecb3d53ea92ceb2fbe9f48dc60e6b8f") @@ -9727,11 +11164,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.9-osx64.tar.bz2", sha256: Some("91a5c2c1facd5a4931a8682b7d792f7cf4f2ba25cd2e7e44e982139a6d5e4840") @@ -9741,11 +11180,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.9-aarch64.tar.bz2", sha256: Some("5e124455e207425e80731dff317f0432fa0aba1f025845ffca813770e2447e32") @@ -9755,11 +11196,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.9-linux32.tar.bz2", sha256: Some("4b261516c6c59078ab0c8bd7207327a1b97057b4ec1714ed5e79a026f9efd492") @@ -9769,11 +11212,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.9-s390x.tar.bz2", sha256: Some("c6177a0016c9145c7b99fddb5d74cc2e518ccdb216a6deb51ef6a377510cc930") @@ -9783,11 +11228,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.9-linux64.tar.bz2", sha256: Some("08be25ec82fc5d23b78563eda144923517daba481a90af0ace7a047c9c9a3c34") @@ -9797,11 +11244,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.9-win64.zip", sha256: Some("05022baaa55db2b60880f2422312d9e4025e1267303ac57f33e8253559d0be88") @@ -9811,11 +11260,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.8-osx64.tar.bz2", sha256: Some("de1b283ff112d76395c0162a1cf11528e192bdc230ee3f1b237f7694c7518dee") @@ -9825,11 +11276,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.8-aarch64-portable.tar.bz2", sha256: Some("0210536e9f1841ba283c13b04783394050837bb3e6f4091c9f1bd9c7f2b94b55") @@ -9839,11 +11292,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.8-linux32.tar.bz2", sha256: Some("bea4b275decd492af6462157d293dd6fcf08a949859f8aec0959537b40afd032") @@ -9853,11 +11308,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.8-s390x.tar.bz2", sha256: Some("ad53d373d6e275a41ca64da7d88afb6a17e48e7bfb2a6fff92daafdc06da6b90") @@ -9867,11 +11324,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.8-linux64.tar.bz2", sha256: Some("089f8e3e357d6130815964ddd3507c13bd53e4976ccf0a89b5c36a9a6775a188") @@ -9881,11 +11340,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 8, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.8-v7.3.8-win64.zip", sha256: Some("0894c468e7de758c509a602a28ef0ba4fbf197ccdf946c7853a7283d9bb2a345") @@ -9895,11 +11356,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2", sha256: Some("12d92f578a200d50959e55074b20f29f93c538943e9a6e6522df1a1cc9cef542") @@ -9909,11 +11372,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.9-aarch64.tar.bz2", sha256: Some("dfc62f2c453fb851d10a1879c6e75c31ffebbf2a44d181bb06fcac4750d023fc") @@ -9923,11 +11388,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux32.tar.bz2", sha256: Some("3398cece0167b81baa219c9cd54a549443d8c0a6b553ec8ec13236281e0d86cd") @@ -9937,11 +11404,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.9-s390x.tar.bz2", sha256: Some("fcab3b9e110379948217cf592229542f53c33bfe881006f95ce30ac815a6df48") @@ -9951,11 +11420,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux64.tar.bz2", sha256: Some("c58195124d807ecc527499ee19bc511ed753f4f2e418203ca51bc7e3b124d5d1") @@ -9965,11 +11436,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 13, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip", sha256: Some("8acb184b48fb3c854de0662e4d23a66b90e73b1ab73a86695022c12c745d8b00") @@ -9979,11 +11452,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.8-osx64.tar.bz2", sha256: Some("76b8eef5b059a7e478f525615482d2a6e9feb83375e3f63c16381d80521a693f") @@ -9993,11 +11468,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.8-aarch64-portable.tar.bz2", sha256: Some("639c76f128a856747aee23a34276fa101a7a157ea81e76394fbaf80b97dcf2f2") @@ -10007,11 +11484,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.8-linux32.tar.bz2", sha256: Some("38429ec6ea1aca391821ee4fbda7358ae86de4600146643f2af2fe2c085af839") @@ -10021,11 +11500,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.8-s390x.tar.bz2", sha256: Some("5c2cd3f7cf04cb96f6bcc6b02e271f5d7275867763978e66651b8d1605ef3141") @@ -10035,11 +11516,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.8-linux64.tar.bz2", sha256: Some("409085db79a6d90bfcf4f576dca1538498e65937acfbe03bd4909bdc262ff378") @@ -10049,11 +11532,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 12, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.8-win64.zip", sha256: Some("96df67492bc8d62b2e71dddf5f6c58965a26cac9799c5f4081401af0494b3bcc") @@ -10063,11 +11548,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.5-osx64.tar.bz2", sha256: Some("b3a7d3099ad83de7c267bb79ae609d5ce73b01800578ffd91ba7e221b13f80db") @@ -10077,11 +11564,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.5-aarch64.tar.bz2", sha256: Some("85d83093b3ef5b863f641bc4073d057cc98bb821e16aa9361a5ff4898e70e8ee") @@ -10091,11 +11580,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.5-linux32.tar.bz2", sha256: Some("3dd8b565203d372829e53945c599296fa961895130342ea13791b17c84ed06c4") @@ -10105,11 +11596,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.5-s390x.tar.bz2", sha256: Some("dffdf5d73613be2c6809dc1a3cf3ee6ac2f3af015180910247ff24270b532ed5") @@ -10119,11 +11612,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.5-linux64.tar.bz2", sha256: Some("9000db3e87b54638e55177e68cbeb30a30fe5d17b6be48a9eb43d65b3ebcfc26") @@ -10133,11 +11628,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 10, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.5-win64.zip", sha256: Some("072bd22427178dc4e65d961f50281bd2f56e11c4e4d9f16311c703f69f46ae24") @@ -10147,11 +11644,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Darwin), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.3-osx64.tar.bz2", sha256: Some("d72b27d5bb60813273f14f07378a08822186a66e216c5d1a768ad295b582438d") @@ -10161,11 +11660,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.3-aarch64.tar.bz2", sha256: Some("ee4aa041558b58de6063dd6df93b3def221c4ca4c900d6a9db5b1b52135703a8") @@ -10175,11 +11676,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.3-linux32.tar.bz2", sha256: Some("7d81b8e9fcd07c067cfe2f519ab770ec62928ee8787f952cadf2d2786246efc8") @@ -10189,11 +11692,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::S390x), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.3-s390x.tar.bz2", sha256: Some("92000d90b9a37f2e9cb7885f2a872adfa9e48e74bf7f84a8b8185c8181f0502d") @@ -10203,11 +11708,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_64), os: Os(target_lexicon::OperatingSystem::Linux), libc: Libc::Some(target_lexicon::Environment::Gnu), + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.3-linux64.tar.bz2", sha256: Some("37e2804c4661c86c857d709d28c7de716b000d31e89766599fdf5a98928b7096") @@ -10217,11 +11724,13 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: 3, minor: 7, patch: 9, - prerelease: Cow::Borrowed(""), + prerelease: None, implementation: LenientImplementationName::Known(ImplementationName::PyPy), arch: Arch(target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)), os: Os(target_lexicon::OperatingSystem::Windows), libc: Libc::None, + variant: PythonVariant::Default + }, url: "https://downloads.python.org/pypy/pypy3.7-v7.3.3-win32.zip", sha256: Some("a282ce40aa4f853e877a5dbb38f0a586a29e563ae9ba82fd50c7e5dc465fb649") diff --git a/crates/uv-python/src/downloads.inc.mustache b/crates/uv-python/src/downloads.inc.mustache index 0a9c1904c..2de417e78 100644 --- a/crates/uv-python/src/downloads.inc.mustache +++ b/crates/uv-python/src/downloads.inc.mustache @@ -1,9 +1,8 @@ -// DO NOT EDIT -// // Generated with `{{generated_with}}` // From template at `{{generated_from}}` -use std::borrow::Cow; +use uv_pep440::{Prerelease, PrereleaseKind}; +use crate::PythonVariant; pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ {{#versions}} @@ -12,7 +11,7 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ major: {{value.major}}, minor: {{value.minor}}, patch: {{value.patch}}, - prerelease: Cow::Borrowed("{{value.prerelease}}"), + prerelease: {{value.prerelease}}, implementation: LenientImplementationName::Known(ImplementationName::{{value.name}}), arch: Arch(target_lexicon::Architecture::{{value.arch}}), os: Os(target_lexicon::OperatingSystem::{{value.os}}), @@ -22,6 +21,8 @@ pub(crate) const PYTHON_DOWNLOADS: &[ManagedPythonDownload] = &[ {{^value.libc}} libc: Libc::None, {{/value.libc}} + variant: {{value.variant}} + }, url: "{{value.url}}", {{#value.sha256}} diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index 1c919e89c..e9679def0 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -17,6 +17,7 @@ use uv_distribution_filename::{ExtensionError, SourceDistExtension}; use uv_extract::hash::Hasher; use uv_fs::{rename_with_retry, Simplified}; use uv_pypi_types::{HashAlgorithm, HashDigest}; +use uv_static::EnvVars; use crate::implementation::{ Error as ImplementationError, ImplementationName, LenientImplementationName, @@ -260,19 +261,25 @@ impl PythonDownloadRequest { return false; } } - if let Some(version) = &self.version { - if !version.matches_major_minor_patch(key.major, key.minor, key.patch) { - return false; - } - if version.is_freethreaded() { - debug!("Installing managed free-threaded Python is not yet supported"); - return false; - } - } // If we don't allow pre-releases, don't match a key with a pre-release tag - if !self.allows_prereleases() && !key.prerelease.is_empty() { + if !self.allows_prereleases() && key.prerelease.is_some() { return false; } + if let Some(version) = &self.version { + if !version.matches_major_minor_patch_prerelease( + key.major, + key.minor, + key.patch, + key.prerelease, + ) { + return false; + } + if let Some(variant) = version.variant() { + if variant != key.variant { + return false; + } + } + } true } @@ -574,11 +581,11 @@ impl ManagedPythonDownload { fn download_url(&self) -> Result { match self.key.implementation { LenientImplementationName::Known(ImplementationName::CPython) => { - if let Ok(mirror) = std::env::var("UV_PYTHON_INSTALL_MIRROR") { + if let Ok(mirror) = std::env::var(EnvVars::UV_PYTHON_INSTALL_MIRROR) { let Some(suffix) = self.url.strip_prefix( "https://github.com/indygreg/python-build-standalone/releases/download/", ) else { - return Err(Error::Mirror("UV_PYTHON_INSTALL_MIRROR", self.url)); + return Err(Error::Mirror(EnvVars::UV_PYTHON_INSTALL_MIRROR, self.url)); }; return Ok(Url::parse( format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(), @@ -587,10 +594,10 @@ impl ManagedPythonDownload { } LenientImplementationName::Known(ImplementationName::PyPy) => { - if let Ok(mirror) = std::env::var("UV_PYPY_INSTALL_MIRROR") { + if let Ok(mirror) = std::env::var(EnvVars::UV_PYPY_INSTALL_MIRROR) { let Some(suffix) = self.url.strip_prefix("https://downloads.python.org/pypy/") else { - return Err(Error::Mirror("UV_PYPY_INSTALL_MIRROR", self.url)); + return Err(Error::Mirror(EnvVars::UV_PYPY_INSTALL_MIRROR, self.url)); }; return Ok(Url::parse( format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(), diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index 0ab0854db..8aec1fc7f 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::fmt; use std::str::FromStr; @@ -6,7 +5,7 @@ use tracing::{debug, info}; use uv_cache::Cache; use uv_client::BaseClientBuilder; -use uv_pep440::Version; +use uv_pep440::{Prerelease, Version}; use crate::discovery::{ find_best_python_installation, find_python_installation, EnvironmentPreference, PythonRequest, @@ -17,7 +16,7 @@ use crate::managed::{ManagedPythonInstallation, ManagedPythonInstallations}; use crate::platform::{Arch, Libc, Os}; use crate::{ downloads, Error, ImplementationName, Interpreter, PythonDownloads, PythonPreference, - PythonSource, PythonVersion, + PythonSource, PythonVariant, PythonVersion, }; /// A Python interpreter and accompanying tools. @@ -224,10 +223,11 @@ pub struct PythonInstallationKey { pub(crate) major: u8, pub(crate) minor: u8, pub(crate) patch: u8, - pub(crate) prerelease: Cow<'static, str>, + pub(crate) prerelease: Option, pub(crate) os: Os, pub(crate) arch: Arch, pub(crate) libc: Libc, + pub(crate) variant: PythonVariant, } impl PythonInstallationKey { @@ -236,20 +236,22 @@ impl PythonInstallationKey { major: u8, minor: u8, patch: u8, - prerelease: String, + prerelease: Option, os: Os, arch: Arch, libc: Libc, + variant: PythonVariant, ) -> Self { Self { implementation, major, minor, patch, - prerelease: Cow::Owned(prerelease), + prerelease, os, arch, libc, + variant, } } @@ -259,16 +261,18 @@ impl PythonInstallationKey { os: Os, arch: Arch, libc: Libc, + variant: PythonVariant, ) -> Self { Self { implementation, major: version.major(), minor: version.minor(), patch: version.patch().unwrap_or_default(), - prerelease: Cow::Owned(version.pre().map(|pre| pre.to_string()).unwrap_or_default()), + prerelease: version.pre(), os, arch, libc, + variant, } } @@ -279,7 +283,12 @@ impl PythonInstallationKey { pub fn version(&self) -> PythonVersion { PythonVersion::from_str(&format!( "{}.{}.{}{}", - self.major, self.minor, self.patch, self.prerelease + self.major, + self.minor, + self.patch, + self.prerelease + .map(|pre| pre.to_string()) + .unwrap_or_default() )) .expect("Python installation keys must have valid Python versions") } @@ -299,14 +308,21 @@ impl PythonInstallationKey { impl fmt::Display for PythonInstallationKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let variant = match self.variant { + PythonVariant::Default => String::new(), + PythonVariant::Freethreaded => format!("+{}", self.variant), + }; write!( f, - "{}-{}.{}.{}{}-{}-{}-{}", + "{}-{}.{}.{}{}{}-{}-{}-{}", self.implementation, self.major, self.minor, self.patch, - self.prerelease, + self.prerelease + .map(|pre| pre.to_string()) + .unwrap_or_default(), + variant, self.os, self.arch, self.libc @@ -343,6 +359,19 @@ impl FromStr for PythonInstallationKey { PythonInstallationKeyError::ParseError(key.to_string(), format!("invalid libc: {err}")) })?; + let (version, variant) = match version.split_once('+') { + Some((version, variant)) => { + let variant = PythonVariant::from_str(variant).map_err(|()| { + PythonInstallationKeyError::ParseError( + key.to_string(), + format!("invalid Python variant: {variant}"), + ) + })?; + (version, variant) + } + None => (*version, PythonVariant::Default), + }; + let version = PythonVersion::from_str(version).map_err(|err| { PythonInstallationKeyError::ParseError( key.to_string(), @@ -356,6 +385,7 @@ impl FromStr for PythonInstallationKey { os, arch, libc, + variant, )) } } diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 6ce6d87e3..cbf7950f6 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -26,7 +26,8 @@ use crate::implementation::LenientImplementationName; use crate::platform::{Arch, Libc, Os}; use crate::pointer_size::PointerSize; use crate::{ - Prefix, PythonInstallationKey, PythonVersion, Target, VersionRequest, VirtualEnvironment, + Prefix, PythonInstallationKey, PythonVariant, PythonVersion, Target, VersionRequest, + VirtualEnvironment, }; /// A Python executable and its associated platform markers. @@ -157,16 +158,22 @@ impl Interpreter { self.python_major(), self.python_minor(), self.python_patch(), - self.python_version() - .pre() - .map(|pre| pre.to_string()) - .unwrap_or_default(), + self.python_version().pre(), self.os(), self.arch(), self.libc(), + self.variant(), ) } + pub fn variant(&self) -> PythonVariant { + if self.gil_disabled() { + PythonVariant::Freethreaded + } else { + PythonVariant::default() + } + } + /// Return the [`Arch`] reported by the interpreter platform tags. pub fn arch(&self) -> Arch { Arch::from(&self.platform().arch()) @@ -795,108 +802,4 @@ impl InterpreterInfo { #[cfg(unix)] #[cfg(test)] -mod tests { - use std::str::FromStr; - - use fs_err as fs; - use indoc::{formatdoc, indoc}; - use tempfile::tempdir; - - use uv_cache::Cache; - use uv_pep440::Version; - - use crate::Interpreter; - - #[test] - fn test_cache_invalidation() { - let mock_dir = tempdir().unwrap(); - let mocked_interpreter = mock_dir.path().join("python"); - let json = indoc! {r##" - { - "result": "success", - "platform": { - "os": { - "name": "manylinux", - "major": 2, - "minor": 38 - }, - "arch": "x86_64" - }, - "manylinux_compatible": false, - "markers": { - "implementation_name": "cpython", - "implementation_version": "3.12.0", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "6.5.0-13-generic", - "platform_system": "Linux", - "platform_version": "#13-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 3 12:16:05 UTC 2023", - "python_full_version": "3.12.0", - "python_version": "3.12", - "sys_platform": "linux" - }, - "sys_base_exec_prefix": "/home/ferris/.pyenv/versions/3.12.0", - "sys_base_prefix": "/home/ferris/.pyenv/versions/3.12.0", - "sys_prefix": "/home/ferris/projects/uv/.venv", - "sys_executable": "/home/ferris/projects/uv/.venv/bin/python", - "sys_path": [ - "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/lib/python3.12", - "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages" - ], - "stdlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12", - "scheme": { - "data": "/home/ferris/.pyenv/versions/3.12.0", - "include": "/home/ferris/.pyenv/versions/3.12.0/include", - "platlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages", - "purelib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages", - "scripts": "/home/ferris/.pyenv/versions/3.12.0/bin" - }, - "virtualenv": { - "data": "", - "include": "include", - "platlib": "lib/python3.12/site-packages", - "purelib": "lib/python3.12/site-packages", - "scripts": "bin" - }, - "pointer_size": "64", - "gil_disabled": true - } - "##}; - - let cache = Cache::temp().unwrap().init().unwrap(); - - fs::write( - &mocked_interpreter, - formatdoc! {r##" - #!/bin/bash - echo '{json}' - "##}, - ) - .unwrap(); - - fs::set_permissions( - &mocked_interpreter, - std::os::unix::fs::PermissionsExt::from_mode(0o770), - ) - .unwrap(); - let interpreter = Interpreter::query(&mocked_interpreter, &cache).unwrap(); - assert_eq!( - interpreter.markers.python_version().version, - Version::from_str("3.12").unwrap() - ); - fs::write( - &mocked_interpreter, - formatdoc! {r##" - #!/bin/bash - echo '{}' - "##, json.replace("3.12", "3.13")}, - ) - .unwrap(); - let interpreter = Interpreter::query(&mocked_interpreter, &cache).unwrap(); - assert_eq!( - interpreter.markers.python_version().version, - Version::from_str("3.13").unwrap() - ); - } -} +mod tests; diff --git a/crates/uv-python/src/interpreter/tests.rs b/crates/uv-python/src/interpreter/tests.rs new file mode 100644 index 000000000..100d0d1a1 --- /dev/null +++ b/crates/uv-python/src/interpreter/tests.rs @@ -0,0 +1,103 @@ +use std::str::FromStr; + +use fs_err as fs; +use indoc::{formatdoc, indoc}; +use tempfile::tempdir; + +use uv_cache::Cache; +use uv_pep440::Version; + +use crate::Interpreter; + +#[test] +fn test_cache_invalidation() { + let mock_dir = tempdir().unwrap(); + let mocked_interpreter = mock_dir.path().join("python"); + let json = indoc! {r##" + { + "result": "success", + "platform": { + "os": { + "name": "manylinux", + "major": 2, + "minor": 38 + }, + "arch": "x86_64" + }, + "manylinux_compatible": false, + "markers": { + "implementation_name": "cpython", + "implementation_version": "3.12.0", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "6.5.0-13-generic", + "platform_system": "Linux", + "platform_version": "#13-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 3 12:16:05 UTC 2023", + "python_full_version": "3.12.0", + "python_version": "3.12", + "sys_platform": "linux" + }, + "sys_base_exec_prefix": "/home/ferris/.pyenv/versions/3.12.0", + "sys_base_prefix": "/home/ferris/.pyenv/versions/3.12.0", + "sys_prefix": "/home/ferris/projects/uv/.venv", + "sys_executable": "/home/ferris/projects/uv/.venv/bin/python", + "sys_path": [ + "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/lib/python3.12", + "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages" + ], + "stdlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12", + "scheme": { + "data": "/home/ferris/.pyenv/versions/3.12.0", + "include": "/home/ferris/.pyenv/versions/3.12.0/include", + "platlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages", + "purelib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages", + "scripts": "/home/ferris/.pyenv/versions/3.12.0/bin" + }, + "virtualenv": { + "data": "", + "include": "include", + "platlib": "lib/python3.12/site-packages", + "purelib": "lib/python3.12/site-packages", + "scripts": "bin" + }, + "pointer_size": "64", + "gil_disabled": true + } + "##}; + + let cache = Cache::temp().unwrap().init().unwrap(); + + fs::write( + &mocked_interpreter, + formatdoc! {r##" + #!/bin/bash + echo '{json}' + "##}, + ) + .unwrap(); + + fs::set_permissions( + &mocked_interpreter, + std::os::unix::fs::PermissionsExt::from_mode(0o770), + ) + .unwrap(); + let interpreter = Interpreter::query(&mocked_interpreter, &cache).unwrap(); + assert_eq!( + interpreter.markers.python_version().version, + Version::from_str("3.12").unwrap() + ); + fs::write( + &mocked_interpreter, + formatdoc! {r##" + #!/bin/bash + echo '{}' + "##, json.replace("3.12", "3.13")}, + ) + .unwrap(); + let interpreter = Interpreter::query(&mocked_interpreter, &cache).unwrap(); + assert_eq!( + interpreter.markers.python_version().version, + Version::from_str("3.13").unwrap() + ); +} diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index fc691ddf7..3cad5989c 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -1,6 +1,9 @@ //! Find requested Python interpreters and query interpreters for information. use thiserror::Error; +#[cfg(test)] +use uv_static::EnvVars; + pub use crate::discovery::{ find_python_installations, EnvironmentPreference, Error as DiscoveryError, PythonDownloads, PythonNotFound, PythonPreference, PythonRequest, PythonSource, PythonVariant, VersionRequest, @@ -37,7 +40,6 @@ mod python_version; mod target; mod version_files; mod virtualenv; -mod which; #[cfg(not(test))] pub(crate) fn current_dir() -> Result { @@ -46,7 +48,7 @@ pub(crate) fn current_dir() -> Result { #[cfg(test)] pub(crate) fn current_dir() -> Result { - std::env::var_os("PWD") + std::env::var_os(EnvVars::PWD) .map(std::path::PathBuf::from) .map(Ok) .unwrap_or(std::env::current_dir()) @@ -86,2263 +88,4 @@ pub enum Error { // The mock interpreters are not valid on Windows so we don't have unit test coverage there // TODO(zanieb): We should write a mock interpreter script that works on Windows #[cfg(all(test, unix))] -mod tests { - use std::{ - env, - ffi::{OsStr, OsString}, - path::{Path, PathBuf}, - str::FromStr, - }; - - use anyhow::Result; - use assert_fs::{fixture::ChildPath, prelude::*, TempDir}; - use indoc::{formatdoc, indoc}; - use temp_env::with_vars; - use test_log::test; - - use uv_cache::Cache; - - use crate::{ - discovery::{ - find_best_python_installation, find_python_installation, EnvironmentPreference, - }, - PythonPreference, - }; - use crate::{ - implementation::ImplementationName, installation::PythonInstallation, - managed::ManagedPythonInstallations, virtualenv::virtualenv_python_executable, - PythonNotFound, PythonRequest, PythonSource, PythonVersion, - }; - - struct TestContext { - tempdir: TempDir, - cache: Cache, - installations: ManagedPythonInstallations, - search_path: Option>, - workdir: ChildPath, - } - - impl TestContext { - fn new() -> Result { - let tempdir = TempDir::new()?; - let workdir = tempdir.child("workdir"); - workdir.create_dir_all()?; - - Ok(Self { - tempdir, - cache: Cache::temp()?, - installations: ManagedPythonInstallations::temp()?, - search_path: None, - workdir, - }) - } - - /// Clear the search path. - fn reset_search_path(&mut self) { - self.search_path = None; - } - - /// Add a directory to the search path. - fn add_to_search_path(&mut self, path: PathBuf) { - match self.search_path.as_mut() { - Some(paths) => paths.push(path), - None => self.search_path = Some(vec![path]), - }; - } - - /// Create a new directory and add it to the search path. - fn new_search_path_directory(&mut self, name: impl AsRef) -> Result { - let child = self.tempdir.child(name); - child.create_dir_all()?; - self.add_to_search_path(child.to_path_buf()); - Ok(child) - } - - fn run(&self, closure: F) -> R - where - F: FnOnce() -> R, - { - self.run_with_vars(&[], closure) - } - - fn run_with_vars(&self, vars: &[(&str, Option<&OsStr>)], closure: F) -> R - where - F: FnOnce() -> R, - { - let path = self - .search_path - .as_ref() - .map(|paths| env::join_paths(paths).unwrap()); - - let mut run_vars = vec![ - // Ensure `PATH` is used - ("UV_TEST_PYTHON_PATH", None), - // Ignore active virtual environments (i.e. that the dev is using) - ("VIRTUAL_ENV", None), - ("PATH", path.as_deref()), - // Use the temporary python directory - ( - "UV_PYTHON_INSTALL_DIR", - Some(self.installations.root().as_os_str()), - ), - // Set a working directory - ("PWD", Some(self.workdir.path().as_os_str())), - ]; - for (key, value) in vars { - run_vars.push((key, *value)); - } - with_vars(&run_vars, closure) - } - - /// Create a fake Python interpreter executable which returns fixed metadata mocking our interpreter - /// query script output. - fn create_mock_interpreter( - path: &Path, - version: &PythonVersion, - implementation: ImplementationName, - system: bool, - free_threaded: bool, - ) -> Result<()> { - let json = indoc! {r##" - { - "result": "success", - "platform": { - "os": { - "name": "manylinux", - "major": 2, - "minor": 38 - }, - "arch": "x86_64" - }, - "manylinux_compatible": true, - "markers": { - "implementation_name": "{IMPLEMENTATION}", - "implementation_version": "{FULL_VERSION}", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "{IMPLEMENTATION}", - "platform_release": "6.5.0-13-generic", - "platform_system": "Linux", - "platform_version": "#13-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 3 12:16:05 UTC 2023", - "python_full_version": "{FULL_VERSION}", - "python_version": "{VERSION}", - "sys_platform": "linux" - }, - "sys_base_exec_prefix": "/home/ferris/.pyenv/versions/{FULL_VERSION}", - "sys_base_prefix": "/home/ferris/.pyenv/versions/{FULL_VERSION}", - "sys_prefix": "{PREFIX}", - "sys_executable": "{PATH}", - "sys_path": [ - "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}/lib/python{VERSION}", - "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}/site-packages" - ], - "stdlib": "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}", - "scheme": { - "data": "/home/ferris/.pyenv/versions/{FULL_VERSION}", - "include": "/home/ferris/.pyenv/versions/{FULL_VERSION}/include", - "platlib": "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}/site-packages", - "purelib": "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}/site-packages", - "scripts": "/home/ferris/.pyenv/versions/{FULL_VERSION}/bin" - }, - "virtualenv": { - "data": "", - "include": "include", - "platlib": "lib/python{VERSION}/site-packages", - "purelib": "lib/python{VERSION}/site-packages", - "scripts": "bin" - }, - "pointer_size": "64", - "gil_disabled": {FREE_THREADED} - } - "##}; - - let json = if system { - json.replace("{PREFIX}", "/home/ferris/.pyenv/versions/{FULL_VERSION}") - } else { - json.replace("{PREFIX}", "/home/ferris/projects/uv/.venv") - }; - - let json = json - .replace( - "{PATH}", - path.to_str().expect("Path can be represented as string"), - ) - .replace("{FULL_VERSION}", &version.to_string()) - .replace("{VERSION}", &version.without_patch().to_string()) - .replace("{FREE_THREADED}", &free_threaded.to_string()) - .replace("{IMPLEMENTATION}", (&implementation).into()); - - fs_err::create_dir_all(path.parent().unwrap())?; - fs_err::write( - path, - formatdoc! {r##" - #!/bin/bash - echo '{json}' - "##}, - )?; - - fs_err::set_permissions(path, std::os::unix::fs::PermissionsExt::from_mode(0o770))?; - - Ok(()) - } - - /// Create a mock Python 2 interpreter executable which returns a fixed error message mocking - /// invocation of Python 2 with the `-I` flag as done by our query script. - fn create_mock_python2_interpreter(path: &Path) -> Result<()> { - let output = indoc! { r" - Unknown option: -I - usage: /usr/bin/python [option] ... [-c cmd | -m mod | file | -] [arg] ... - Try `python -h` for more information. - "}; - - fs_err::write( - path, - formatdoc! {r##" - #!/bin/bash - echo '{output}' 1>&2 - "##}, - )?; - - fs_err::set_permissions(path, std::os::unix::fs::PermissionsExt::from_mode(0o770))?; - - Ok(()) - } - - /// Create child directories in a temporary directory. - fn new_search_path_directories( - &mut self, - names: &[impl AsRef], - ) -> Result> { - let paths = names - .iter() - .map(|name| self.new_search_path_directory(name)) - .collect::>>()?; - Ok(paths) - } - - /// Create fake Python interpreters the given Python versions. - /// - /// Adds them to the test context search path. - fn add_python_to_workdir(&self, name: &str, version: &str) -> Result<()> { - TestContext::create_mock_interpreter( - self.workdir.child(name).as_ref(), - &PythonVersion::from_str(version).expect("Test uses valid version"), - ImplementationName::default(), - true, - false, - ) - } - - /// Create fake Python interpreters the given Python versions. - /// - /// Adds them to the test context search path. - fn add_python_versions(&mut self, versions: &[&'static str]) -> Result<()> { - let interpreters: Vec<_> = versions - .iter() - .map(|version| (true, ImplementationName::default(), "python", *version)) - .collect(); - self.add_python_interpreters(interpreters.as_slice()) - } - - /// Create fake Python interpreters the given Python implementations and versions. - /// - /// Adds them to the test context search path. - fn add_python_interpreters( - &mut self, - kinds: &[(bool, ImplementationName, &'static str, &'static str)], - ) -> Result<()> { - // Generate a "unique" folder name for each interpreter - let names: Vec = kinds - .iter() - .map(|(system, implementation, name, version)| { - OsString::from_str(&format!("{system}-{implementation}-{name}-{version}")) - .unwrap() - }) - .collect(); - let paths = self.new_search_path_directories(names.as_slice())?; - for (path, (system, implementation, executable, version)) in - itertools::zip_eq(&paths, kinds) - { - let python = format!("{executable}{}", env::consts::EXE_SUFFIX); - Self::create_mock_interpreter( - &path.join(python), - &PythonVersion::from_str(version).unwrap(), - *implementation, - *system, - false, - )?; - } - Ok(()) - } - - /// Create a mock virtual environment at the given directory - fn mock_venv(path: impl AsRef, version: &'static str) -> Result<()> { - let executable = virtualenv_python_executable(path.as_ref()); - fs_err::create_dir_all( - executable - .parent() - .expect("A Python executable path should always have a parent"), - )?; - TestContext::create_mock_interpreter( - &executable, - &PythonVersion::from_str(version) - .expect("A valid Python version is used for tests"), - ImplementationName::default(), - false, - false, - )?; - ChildPath::new(path.as_ref().join("pyvenv.cfg")).touch()?; - Ok(()) - } - - /// Create a mock conda prefix at the given directory. - /// - /// These are like virtual environments but they look like system interpreters because `prefix` and `base_prefix` are equal. - fn mock_conda_prefix(path: impl AsRef, version: &'static str) -> Result<()> { - let executable = virtualenv_python_executable(&path); - fs_err::create_dir_all( - executable - .parent() - .expect("A Python executable path should always have a parent"), - )?; - TestContext::create_mock_interpreter( - &executable, - &PythonVersion::from_str(version) - .expect("A valid Python version is used for tests"), - ImplementationName::default(), - true, - false, - )?; - ChildPath::new(path.as_ref().join("pyvenv.cfg")).touch()?; - Ok(()) - } - } - - #[test] - fn find_python_empty_path() -> Result<()> { - let mut context = TestContext::new()?; - - context.search_path = Some(vec![]); - let result = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::default(), - &context.cache, - ) - }); - assert!( - matches!(result, Ok(Err(PythonNotFound { .. }))), - "With an empty path, no Python installation should be detected got {result:?}" - ); - - context.search_path = None; - let result = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::default(), - &context.cache, - ) - }); - assert!( - matches!(result, Ok(Err(PythonNotFound { .. }))), - "With an unset path, no Python installation should be detected got {result:?}" - ); - - Ok(()) - } - - #[test] - fn find_python_unexecutable_file() -> Result<()> { - let mut context = TestContext::new()?; - context - .new_search_path_directory("path")? - .child(format!("python{}", env::consts::EXE_SUFFIX)) - .touch()?; - - let result = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::default(), - &context.cache, - ) - }); - assert!( - matches!( - result, - Ok(Err(PythonNotFound { .. })) - ), - "With an non-executable Python, no Python installation should be detected; got {result:?}" - ); - - Ok(()) - } - - #[test] - fn find_python_valid_executable() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.12.1"])?; - - let interpreter = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::default(), - &context.cache, - ) - })??; - assert!( - matches!( - interpreter, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should find the valid executable; got {interpreter:?}" - ); - - Ok(()) - } - - #[test] - fn find_python_valid_executable_after_invalid() -> Result<()> { - let mut context = TestContext::new()?; - let children = context.new_search_path_directories(&[ - "query-parse-error", - "not-executable", - "empty", - "good", - ])?; - - // An executable file with a bad response - #[cfg(unix)] - fs_err::write( - children[0].join(format!("python{}", env::consts::EXE_SUFFIX)), - formatdoc! {r##" - #!/bin/bash - echo 'foo' - "##}, - )?; - fs_err::set_permissions( - children[0].join(format!("python{}", env::consts::EXE_SUFFIX)), - std::os::unix::fs::PermissionsExt::from_mode(0o770), - )?; - - // A non-executable file - ChildPath::new(children[1].join(format!("python{}", env::consts::EXE_SUFFIX))).touch()?; - - // An empty directory at `children[2]` - - // An good interpreter! - let python_path = children[3].join(format!("python{}", env::consts::EXE_SUFFIX)); - TestContext::create_mock_interpreter( - &python_path, - &PythonVersion::from_str("3.12.1").unwrap(), - ImplementationName::default(), - true, - false, - )?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::default(), - &context.cache, - ) - })??; - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should skip the bad executables in favor of the good one; got {python:?}" - ); - assert_eq!(python.interpreter().sys_executable(), python_path); - - Ok(()) - } - - #[test] - fn find_python_only_python2_executable() -> Result<()> { - let mut context = TestContext::new()?; - let python = context - .new_search_path_directory("python2")? - .child(format!("python{}", env::consts::EXE_SUFFIX)); - TestContext::create_mock_python2_interpreter(&python)?; - - let result = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::default(), - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - // TODO(zanieb): We could improve the error handling to hint this to the user - "If only Python 2 is available, we should not find a python; got {result:?}" - ); - - Ok(()) - } - - #[test] - fn find_python_skip_python2_executable() -> Result<()> { - let mut context = TestContext::new()?; - - let python2 = context - .new_search_path_directory("python2")? - .child(format!("python{}", env::consts::EXE_SUFFIX)); - TestContext::create_mock_python2_interpreter(&python2)?; - - let python3 = context - .new_search_path_directory("python3")? - .child(format!("python{}", env::consts::EXE_SUFFIX)); - TestContext::create_mock_interpreter( - &python3, - &PythonVersion::from_str("3.12.1").unwrap(), - ImplementationName::default(), - true, - false, - )?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::default(), - &context.cache, - ) - })??; - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should skip the Python 2 installation and find the Python 3 interpreter; got {python:?}" - ); - assert_eq!(python.interpreter().sys_executable(), python3.path()); - - Ok(()) - } - - #[test] - fn find_python_system_python_allowed() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (false, ImplementationName::CPython, "python", "3.10.0"), - (true, ImplementationName::CPython, "python", "3.10.1"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "Should find the first interpreter regardless of system" - ); - - // Reverse the order of the virtual environment and system - context.reset_search_path(); - context.add_python_interpreters(&[ - (true, ImplementationName::CPython, "python", "3.10.1"), - (false, ImplementationName::CPython, "python", "3.10.0"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "Should find the first interpreter regardless of system" - ); - - Ok(()) - } - - #[test] - fn find_python_system_python_required() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (false, ImplementationName::CPython, "python", "3.10.0"), - (true, ImplementationName::CPython, "python", "3.10.1"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "Should skip the virtual environment" - ); - - Ok(()) - } - - #[test] - fn find_python_system_python_disallowed() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (true, ImplementationName::CPython, "python", "3.10.0"), - (false, ImplementationName::CPython, "python", "3.10.1"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "Should skip the system Python" - ); - - Ok(()) - } - - #[test] - fn find_python_version_minor() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("3.11"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should find a python; got {python:?}" - ); - assert_eq!( - &python.interpreter().python_full_version().to_string(), - "3.11.2", - "We should find the correct interpreter for the request" - ); - - Ok(()) - } - - #[test] - fn find_python_version_patch() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.10.1", "3.11.3", "3.11.2", "3.12.3"])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("3.11.2"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should find a python; got {python:?}" - ); - assert_eq!( - &python.interpreter().python_full_version().to_string(), - "3.11.2", - "We should find the correct interpreter for the request" - ); - - Ok(()) - } - - #[test] - fn find_python_version_minor_no_match() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; - - let result = context.run(|| { - find_python_installation( - &PythonRequest::parse("3.9"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not find a python; got {result:?}" - ); - - Ok(()) - } - - #[test] - fn find_python_version_patch_no_match() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; - - let result = context.run(|| { - find_python_installation( - &PythonRequest::parse("3.11.9"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not find a python; got {result:?}" - ); - - Ok(()) - } - - #[test] - fn find_best_python_version_patch_exact() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.10.1", "3.11.2", "3.11.4", "3.11.3", "3.12.5"])?; - - let python = context.run(|| { - find_best_python_installation( - &PythonRequest::parse("3.11.3"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should find a python; got {python:?}" - ); - assert_eq!( - &python.interpreter().python_full_version().to_string(), - "3.11.3", - "We should prefer the exact request" - ); - - Ok(()) - } - - #[test] - fn find_best_python_version_patch_fallback() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.10.1", "3.11.2", "3.11.4", "3.11.3", "3.12.5"])?; - - let python = context.run(|| { - find_best_python_installation( - &PythonRequest::parse("3.11.11"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should find a python; got {python:?}" - ); - assert_eq!( - &python.interpreter().python_full_version().to_string(), - "3.11.2", - "We should fallback to the first matching minor" - ); - - Ok(()) - } - - #[test] - fn find_best_python_skips_source_without_match() -> Result<()> { - let mut context = TestContext::new()?; - let venv = context.tempdir.child(".venv"); - TestContext::mock_venv(&venv, "3.12.0")?; - context.add_python_versions(&["3.10.1"])?; - - let python = - context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_best_python_installation( - &PythonRequest::parse("3.10"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should skip the active environment in favor of the requested version; got {python:?}" - ); - - Ok(()) - } - - #[test] - fn find_best_python_returns_to_earlier_source_on_fallback() -> Result<()> { - let mut context = TestContext::new()?; - let venv = context.tempdir.child(".venv"); - TestContext::mock_venv(&venv, "3.10.1")?; - context.add_python_versions(&["3.10.3"])?; - - let python = - context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_best_python_installation( - &PythonRequest::parse("3.10.2"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::ActiveEnvironment, - interpreter: _ - } - ), - "We should prefer the active environment after relaxing; got {python:?}" - ); - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should prefer the active environment" - ); - - Ok(()) - } - - #[test] - fn find_python_from_active_python() -> Result<()> { - let context = TestContext::new()?; - let venv = context.tempdir.child("some-venv"); - TestContext::mock_venv(&venv, "3.12.0")?; - - let python = - context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should prefer the active environment" - ); - - Ok(()) - } - - #[test] - fn find_python_from_active_python_prerelease() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.12.0"])?; - let venv = context.tempdir.child("some-venv"); - TestContext::mock_venv(&venv, "3.13.0rc1")?; - - let python = - context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.13.0rc1", - "We should prefer the active environment" - ); - - Ok(()) - } - - #[test] - fn find_python_from_conda_prefix() -> Result<()> { - let context = TestContext::new()?; - let condaenv = context.tempdir.child("condaenv"); - TestContext::mock_conda_prefix(&condaenv, "3.12.0")?; - - let python = - context.run_with_vars(&[("CONDA_PREFIX", Some(condaenv.as_os_str()))], || { - // Note this python is not treated as a system interpreter - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlyVirtual, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should allow the active conda python" - ); - - Ok(()) - } - - #[test] - fn find_python_from_conda_prefix_and_virtualenv() -> Result<()> { - let context = TestContext::new()?; - let venv = context.tempdir.child(".venv"); - TestContext::mock_venv(&venv, "3.12.0")?; - let condaenv = context.tempdir.child("condaenv"); - TestContext::mock_conda_prefix(&condaenv, "3.12.1")?; - - let python = context.run_with_vars( - &[ - ("VIRTUAL_ENV", Some(venv.as_os_str())), - ("CONDA_PREFIX", Some(condaenv.as_os_str())), - ], - || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - }, - )??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should prefer the non-conda python" - ); - - // Put a virtual environment in the working directory - let venv = context.workdir.child(".venv"); - TestContext::mock_venv(venv, "3.12.2")?; - let python = - context.run_with_vars(&[("CONDA_PREFIX", Some(condaenv.as_os_str()))], || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.1", - "We should prefer the conda python over inactive virtual environments" - ); - - Ok(()) - } - - #[test] - fn find_python_from_discovered_python() -> Result<()> { - let mut context = TestContext::new()?; - - // Create a virtual environment in a parent of the workdir - let venv = context.tempdir.child(".venv"); - TestContext::mock_venv(venv, "3.12.0")?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should find the python" - ); - - // Add some system versions to ensure we don't use those - context.add_python_versions(&["3.12.1", "3.12.2"])?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should prefer the discovered virtual environment over available system versions" - ); - - Ok(()) - } - - #[test] - fn find_python_skips_broken_active_python() -> Result<()> { - let context = TestContext::new()?; - let venv = context.tempdir.child(".venv"); - TestContext::mock_venv(&venv, "3.12.0")?; - - // Delete the pyvenv cfg to break the virtualenv - fs_err::remove_file(venv.join("pyvenv.cfg"))?; - - let python = - context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - // TODO(zanieb): We should skip this python, why don't we? - "We should prefer the active environment" - ); - - Ok(()) - } - - #[test] - fn find_python_from_parent_interpreter() -> Result<()> { - let mut context = TestContext::new()?; - - let parent = context.tempdir.child("python").to_path_buf(); - TestContext::create_mock_interpreter( - &parent, - &PythonVersion::from_str("3.12.0").unwrap(), - ImplementationName::CPython, - // Note we mark this as a system interpreter instead of a virtual environment - true, - false, - )?; - - let python = context.run_with_vars( - &[("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str()))], - || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - }, - )??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should find the parent interpreter" - ); - - // Parent interpreters are preferred over virtual environments and system interpreters - let venv = context.tempdir.child(".venv"); - TestContext::mock_venv(&venv, "3.12.2")?; - context.add_python_versions(&["3.12.3"])?; - let python = context.run_with_vars( - &[ - ("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())), - ("VIRTUAL_ENV", Some(venv.as_os_str())), - ], - || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - }, - )??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should prefer the parent interpreter" - ); - - // Test with `EnvironmentPreference::ExplicitSystem` - let python = context.run_with_vars( - &[ - ("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())), - ("VIRTUAL_ENV", Some(venv.as_os_str())), - ], - || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::ExplicitSystem, - PythonPreference::OnlySystem, - &context.cache, - ) - }, - )??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should prefer the parent interpreter" - ); - - // Test with `EnvironmentPreference::OnlySystem` - let python = context.run_with_vars( - &[ - ("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())), - ("VIRTUAL_ENV", Some(venv.as_os_str())), - ], - || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::OnlySystem, - &context.cache, - ) - }, - )??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should prefer the parent interpreter since it's not virtual" - ); - - // Test with `EnvironmentPreference::OnlyVirtual` - let python = context.run_with_vars( - &[ - ("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())), - ("VIRTUAL_ENV", Some(venv.as_os_str())), - ], - || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlyVirtual, - PythonPreference::OnlySystem, - &context.cache, - ) - }, - )??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.2", - "We find the virtual environment Python because a system is explicitly not allowed" - ); - - Ok(()) - } - - #[test] - fn find_python_from_parent_interpreter_prerelease() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.12.0"])?; - let parent = context.tempdir.child("python").to_path_buf(); - TestContext::create_mock_interpreter( - &parent, - &PythonVersion::from_str("3.13.0rc2").unwrap(), - ImplementationName::CPython, - // Note we mark this as a system interpreter instead of a virtual environment - true, - false, - )?; - - let python = context.run_with_vars( - &[("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str()))], - || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - }, - )??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.13.0rc2", - "We should find the parent interpreter" - ); - - Ok(()) - } - - #[test] - fn find_python_active_python_skipped_if_system_required() -> Result<()> { - let mut context = TestContext::new()?; - let venv = context.tempdir.child(".venv"); - TestContext::mock_venv(&venv, "3.9.0")?; - context.add_python_versions(&["3.10.0", "3.11.1", "3.12.2"])?; - - // Without a specific request - let python = - context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlySystem, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should skip the active environment" - ); - - // With a requested minor version - let python = - context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_python_installation( - &PythonRequest::parse("3.12"), - EnvironmentPreference::OnlySystem, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.2", - "We should skip the active environment" - ); - - // With a patch version that cannot be python - let result = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || { - find_python_installation( - &PythonRequest::parse("3.12.3"), - EnvironmentPreference::OnlySystem, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - result.is_err(), - "We should not find an python; got {result:?}" - ); - - Ok(()) - } - - #[test] - fn find_python_fails_if_no_virtualenv_and_system_not_allowed() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_versions(&["3.10.1", "3.11.2"])?; - - let result = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::OnlyVirtual, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not find an python; got {result:?}" - ); - - // With an invalid virtual environment variable - let result = context.run_with_vars( - &[("VIRTUAL_ENV", Some(context.tempdir.as_os_str()))], - || { - find_python_installation( - &PythonRequest::parse("3.12.3"), - EnvironmentPreference::OnlySystem, - PythonPreference::OnlySystem, - &context.cache, - ) - }, - )?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not find an python; got {result:?}" - ); - Ok(()) - } - - #[test] - fn find_python_allows_name_in_working_directory() -> Result<()> { - let context = TestContext::new()?; - context.add_python_to_workdir("foobar", "3.10.0")?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("foobar"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should find the named executable" - ); - - let result = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not find it without a specific request" - ); - - let result = context.run(|| { - find_python_installation( - &PythonRequest::parse("3.10.0"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not find it via a matching version request" - ); - - Ok(()) - } - - #[test] - fn find_python_allows_relative_file_path() -> Result<()> { - let mut context = TestContext::new()?; - let python = context.workdir.child("foo").join("bar"); - TestContext::create_mock_interpreter( - &python, - &PythonVersion::from_str("3.10.0").unwrap(), - ImplementationName::default(), - true, - false, - )?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("./foo/bar"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should find the `bar` executable" - ); - - context.add_python_versions(&["3.11.1"])?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("./foo/bar"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should prefer the `bar` executable over the system and virtualenvs" - ); - - Ok(()) - } - - #[test] - fn find_python_allows_absolute_file_path() -> Result<()> { - let mut context = TestContext::new()?; - let python_path = context.tempdir.child("foo").join("bar"); - TestContext::create_mock_interpreter( - &python_path, - &PythonVersion::from_str("3.10.0").unwrap(), - ImplementationName::default(), - true, - false, - )?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(python_path.to_str().unwrap()), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should find the `bar` executable" - ); - - // With `EnvironmentPreference::ExplicitSystem` - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(python_path.to_str().unwrap()), - EnvironmentPreference::ExplicitSystem, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should allow the `bar` executable with explicit system" - ); - - // With `EnvironmentPreference::OnlyVirtual` - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(python_path.to_str().unwrap()), - EnvironmentPreference::OnlyVirtual, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should allow the `bar` executable and verify it is virtual" - ); - - context.add_python_versions(&["3.11.1"])?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(python_path.to_str().unwrap()), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should prefer the `bar` executable over the system and virtualenvs" - ); - - Ok(()) - } - - #[test] - fn find_python_allows_venv_directory_path() -> Result<()> { - let mut context = TestContext::new()?; - - let venv = context.tempdir.child("foo").child(".venv"); - TestContext::mock_venv(&venv, "3.10.0")?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("../foo/.venv"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should find the relative venv path" - ); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(venv.to_str().unwrap()), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should find the absolute venv path" - ); - - // We should allow it to be a directory that _looks_ like a virtual environment. - let python_path = context.tempdir.child("bar").join("bin").join("python"); - TestContext::create_mock_interpreter( - &python_path, - &PythonVersion::from_str("3.10.0").unwrap(), - ImplementationName::default(), - true, - false, - )?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(context.tempdir.child("bar").to_str().unwrap()), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should find the executable in the directory" - ); - - let other_venv = context.tempdir.child("foobar").child(".venv"); - TestContext::mock_venv(&other_venv, "3.11.1")?; - context.add_python_versions(&["3.12.2"])?; - let python = - context.run_with_vars(&[("VIRTUAL_ENV", Some(other_venv.as_os_str()))], || { - find_python_installation( - &PythonRequest::parse(venv.to_str().unwrap()), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should prefer the requested directory over the system and active virtual environments" - ); - - Ok(()) - } - - #[test] - fn find_python_venv_symlink() -> Result<()> { - let context = TestContext::new()?; - - let venv = context.tempdir.child("target").child("env"); - TestContext::mock_venv(&venv, "3.10.6")?; - let symlink = context.tempdir.child("proj").child(".venv"); - context.tempdir.child("proj").create_dir_all()?; - symlink.symlink_to_dir(venv)?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("../proj/.venv"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.6", - "We should find the symlinked venv" - ); - Ok(()) - } - - #[test] - fn find_python_treats_missing_file_path_as_file() -> Result<()> { - let context = TestContext::new()?; - context.workdir.child("foo").create_dir_all()?; - - let result = context.run(|| { - find_python_installation( - &PythonRequest::parse("./foo/bar"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not find the file; got {result:?}" - ); - - Ok(()) - } - - #[test] - fn find_python_executable_name_in_search_path() -> Result<()> { - let mut context = TestContext::new()?; - let python = context.tempdir.child("foo").join("bar"); - TestContext::create_mock_interpreter( - &python, - &PythonVersion::from_str("3.10.0").unwrap(), - ImplementationName::default(), - true, - false, - )?; - context.add_to_search_path(context.tempdir.child("foo").to_path_buf()); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("bar"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should find the `bar` executable" - ); - - // With [`EnvironmentPreference::OnlyVirtual`], we should not allow the interpreter - let result = context.run(|| { - find_python_installation( - &PythonRequest::parse("bar"), - EnvironmentPreference::ExplicitSystem, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not allow a system interpreter; got {result:?}" - ); - - // Unless it's a virtual environment interpreter - let mut context = TestContext::new()?; - let python = context.tempdir.child("foo").join("bar"); - TestContext::create_mock_interpreter( - &python, - &PythonVersion::from_str("3.10.0").unwrap(), - ImplementationName::default(), - false, // Not a system interpreter - false, - )?; - context.add_to_search_path(context.tempdir.child("foo").to_path_buf()); - - let python = context - .run(|| { - find_python_installation( - &PythonRequest::parse("bar"), - EnvironmentPreference::ExplicitSystem, - PythonPreference::OnlySystem, - &context.cache, - ) - }) - .unwrap() - .unwrap(); - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should find the `bar` executable" - ); - - Ok(()) - } - - #[test] - fn find_python_pypy() -> Result<()> { - let mut context = TestContext::new()?; - - context.add_python_interpreters(&[(true, ImplementationName::PyPy, "pypy", "3.10.0")])?; - let result = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not find the pypy interpreter if not named `python` or requested; got {result:?}" - ); - - // But we should find it - context.reset_search_path(); - context.add_python_interpreters(&[(true, ImplementationName::PyPy, "python", "3.10.1")])?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should find the pypy interpreter if it's the only one" - ); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("pypy"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should find the pypy interpreter if it's requested" - ); - - Ok(()) - } - - #[test] - fn find_python_pypy_request_ignores_cpython() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (true, ImplementationName::CPython, "python", "3.10.0"), - (true, ImplementationName::PyPy, "pypy", "3.10.1"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("pypy"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should skip the CPython interpreter" - ); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should take the first interpreter without a specific request" - ); - - Ok(()) - } - - #[test] - fn find_python_pypy_request_skips_wrong_versions() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (true, ImplementationName::PyPy, "pypy", "3.9"), - (true, ImplementationName::PyPy, "pypy", "3.10.1"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("pypy3.10"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should skip the first interpreter" - ); - - Ok(()) - } - - #[test] - fn find_python_pypy_finds_executable_with_version_name() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (true, ImplementationName::PyPy, "pypy3.9", "3.10.0"), // We don't consider this one because of the executable name - (true, ImplementationName::PyPy, "pypy3.10", "3.10.1"), - (true, ImplementationName::PyPy, "pypy", "3.10.2"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("pypy@3.10"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should find the requested interpreter version" - ); - - Ok(()) - } - - #[test] - fn find_python_all_minors() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (true, ImplementationName::CPython, "python", "3.10.0"), - (true, ImplementationName::CPython, "python3", "3.10.0"), - (true, ImplementationName::CPython, "python3.12", "3.12.0"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(">= 3.11"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0", - "We should find matching minor version even if they aren't called `python` or `python3`" - ); - - Ok(()) - } - - #[test] - fn find_python_all_minors_prerelease() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (true, ImplementationName::CPython, "python", "3.10.0"), - (true, ImplementationName::CPython, "python3", "3.10.0"), - (true, ImplementationName::CPython, "python3.11", "3.11.0b0"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(">= 3.11"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.11.0b0", - "We should find the 3.11 prerelease even though >=3.11 would normally exclude prereleases" - ); - - Ok(()) - } - - #[test] - fn find_python_all_minors_prerelease_next() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (true, ImplementationName::CPython, "python", "3.10.0"), - (true, ImplementationName::CPython, "python3", "3.10.0"), - (true, ImplementationName::CPython, "python3.12", "3.12.0b0"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse(">= 3.11"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.12.0b0", - "We should find the 3.12 prerelease" - ); - - Ok(()) - } - - #[test] - fn find_python_graalpy() -> Result<()> { - let mut context = TestContext::new()?; - - context.add_python_interpreters(&[( - true, - ImplementationName::GraalPy, - "graalpy", - "3.10.0", - )])?; - let result = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })?; - assert!( - matches!(result, Err(PythonNotFound { .. })), - "We should not the graalpy interpreter if not named `python` or requested; got {result:?}" - ); - - // But we should find it - context.reset_search_path(); - context.add_python_interpreters(&[( - true, - ImplementationName::GraalPy, - "python", - "3.10.1", - )])?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should find the graalpy interpreter if it's the only one" - ); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("graalpy"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should find the graalpy interpreter if it's requested" - ); - - Ok(()) - } - - #[test] - fn find_python_graalpy_request_ignores_cpython() -> Result<()> { - let mut context = TestContext::new()?; - context.add_python_interpreters(&[ - (true, ImplementationName::CPython, "python", "3.10.0"), - (true, ImplementationName::GraalPy, "graalpy", "3.10.1"), - ])?; - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("graalpy"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should skip the CPython interpreter" - ); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::Default, - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should take the first interpreter without a specific request" - ); - - Ok(()) - } - - #[test] - fn find_python_prefers_generic_executable_over_implementation_name() -> Result<()> { - let mut context = TestContext::new()?; - - // We prefer `python` executables over `graalpy` executables in the same directory - // if they are both GraalPy - TestContext::create_mock_interpreter( - &context.tempdir.join("python"), - &PythonVersion::from_str("3.10.0").unwrap(), - ImplementationName::GraalPy, - true, - false, - )?; - TestContext::create_mock_interpreter( - &context.tempdir.join("graalpy"), - &PythonVersion::from_str("3.10.1").unwrap(), - ImplementationName::GraalPy, - true, - false, - )?; - context.add_to_search_path(context.tempdir.to_path_buf()); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("graalpy@3.10"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - ); - - // And `python` executables earlier in the search path will take precedence - context.reset_search_path(); - context.add_python_interpreters(&[ - (true, ImplementationName::GraalPy, "python", "3.10.2"), - (true, ImplementationName::GraalPy, "graalpy", "3.10.3"), - ])?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("graalpy@3.10"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.2", - ); - - // But `graalpy` executables earlier in the search path will take precedence - context.reset_search_path(); - context.add_python_interpreters(&[ - (true, ImplementationName::GraalPy, "graalpy", "3.10.3"), - (true, ImplementationName::GraalPy, "python", "3.10.2"), - ])?; - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("graalpy@3.10"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.3", - ); - - Ok(()) - } - - #[test] - fn find_python_prefers_generic_executable_over_one_with_version() -> Result<()> { - let mut context = TestContext::new()?; - TestContext::create_mock_interpreter( - &context.tempdir.join("pypy3.10"), - &PythonVersion::from_str("3.10.0").unwrap(), - ImplementationName::PyPy, - true, - false, - )?; - TestContext::create_mock_interpreter( - &context.tempdir.join("pypy"), - &PythonVersion::from_str("3.10.1").unwrap(), - ImplementationName::PyPy, - true, - false, - )?; - context.add_to_search_path(context.tempdir.to_path_buf()); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("pypy@3.10"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.1", - "We should prefer the generic executable over one with the version number" - ); - - let mut context = TestContext::new()?; - TestContext::create_mock_interpreter( - &context.tempdir.join("python3.10"), - &PythonVersion::from_str("3.10.0").unwrap(), - ImplementationName::PyPy, - true, - false, - )?; - TestContext::create_mock_interpreter( - &context.tempdir.join("pypy"), - &PythonVersion::from_str("3.10.1").unwrap(), - ImplementationName::PyPy, - true, - false, - )?; - context.add_to_search_path(context.tempdir.to_path_buf()); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("pypy@3.10"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - assert_eq!( - python.interpreter().python_full_version().to_string(), - "3.10.0", - "We should prefer the generic name with a version over one the implementation name" - ); - - Ok(()) - } - - #[test] - fn find_python_version_free_threaded() -> Result<()> { - let mut context = TestContext::new()?; - - TestContext::create_mock_interpreter( - &context.tempdir.join("python"), - &PythonVersion::from_str("3.13.1").unwrap(), - ImplementationName::CPython, - true, - false, - )?; - TestContext::create_mock_interpreter( - &context.tempdir.join("python3.13t"), - &PythonVersion::from_str("3.13.0").unwrap(), - ImplementationName::CPython, - true, - true, - )?; - context.add_to_search_path(context.tempdir.to_path_buf()); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("3.13t"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should find a python; got {python:?}" - ); - assert_eq!( - &python.interpreter().python_full_version().to_string(), - "3.13.0", - "We should find the correct interpreter for the request" - ); - assert!( - &python.interpreter().gil_disabled(), - "We should find a python without the GIL" - ); - - Ok(()) - } - - #[test] - fn find_python_version_prefer_non_free_threaded() -> Result<()> { - let mut context = TestContext::new()?; - - TestContext::create_mock_interpreter( - &context.tempdir.join("python"), - &PythonVersion::from_str("3.13.0").unwrap(), - ImplementationName::CPython, - true, - false, - )?; - TestContext::create_mock_interpreter( - &context.tempdir.join("python3.13t"), - &PythonVersion::from_str("3.13.0").unwrap(), - ImplementationName::CPython, - true, - true, - )?; - context.add_to_search_path(context.tempdir.to_path_buf()); - - let python = context.run(|| { - find_python_installation( - &PythonRequest::parse("3.13"), - EnvironmentPreference::Any, - PythonPreference::OnlySystem, - &context.cache, - ) - })??; - - assert!( - matches!( - python, - PythonInstallation { - source: PythonSource::SearchPath, - interpreter: _ - } - ), - "We should find a python; got {python:?}" - ); - assert_eq!( - &python.interpreter().python_full_version().to_string(), - "3.13.0", - "We should find the correct interpreter for the request" - ); - assert!( - !&python.interpreter().gil_disabled(), - "We should prefer a python with the GIL" - ); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-python/src/libc.rs b/crates/uv-python/src/libc.rs index 7e60780f6..15f058999 100644 --- a/crates/uv-python/src/libc.rs +++ b/crates/uv-python/src/libc.rs @@ -235,45 +235,4 @@ fn find_ld_path_at(path: impl AsRef) -> Option { } #[cfg(test)] -mod tests { - use super::*; - use indoc::indoc; - - #[test] - fn parse_ldd_output() { - let ver_str = glibc_ldd_output_to_version( - "stdout", - indoc! {br"ld.so (Ubuntu GLIBC 2.39-0ubuntu8.3) stable release version 2.39. - Copyright (C) 2024 Free Software Foundation, Inc. - This is free software; see the source for copying conditions. - There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A - PARTICULAR PURPOSE. - "}, - ) - .unwrap(); - assert_eq!( - ver_str, - LibcVersion::Manylinux { - major: 2, - minor: 39 - } - ); - } - - #[test] - fn parse_musl_ld_output() { - // This output was generated by running `/lib/ld-musl-x86_64.so.1` - // in an Alpine Docker image. The Alpine version: - // - // # cat /etc/alpine-release - // 3.19.1 - let output = b"\ -musl libc (x86_64) -Version 1.2.4_git20230717 -Dynamic Program Loader -Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname [args]\ - "; - let got = musl_ld_output_to_version("stderr", output).unwrap(); - assert_eq!(got, LibcVersion::Musllinux { major: 1, minor: 2 }); - } -} +mod tests; diff --git a/crates/uv-python/src/libc/tests.rs b/crates/uv-python/src/libc/tests.rs new file mode 100644 index 000000000..f85e97237 --- /dev/null +++ b/crates/uv-python/src/libc/tests.rs @@ -0,0 +1,40 @@ +use super::*; +use indoc::indoc; + +#[test] +fn parse_ldd_output() { + let ver_str = glibc_ldd_output_to_version( + "stdout", + indoc! {br"ld.so (Ubuntu GLIBC 2.39-0ubuntu8.3) stable release version 2.39. + Copyright (C) 2024 Free Software Foundation, Inc. + This is free software; see the source for copying conditions. + There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. + "}, + ) + .unwrap(); + assert_eq!( + ver_str, + LibcVersion::Manylinux { + major: 2, + minor: 39 + } + ); +} + +#[test] +fn parse_musl_ld_output() { + // This output was generated by running `/lib/ld-musl-x86_64.so.1` + // in an Alpine Docker image. The Alpine version: + // + // # cat /etc/alpine-release + // 3.19.1 + let output = b"\ +musl libc (x86_64) +Version 1.2.4_git20230717 +Dynamic Program Loader +Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname [args]\ + "; + let got = musl_ld_output_to_version("stderr", output).unwrap(); + assert_eq!(got, LibcVersion::Musllinux { major: 1, minor: 2 }); +} diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index 7f5d538ff..5c077079d 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -20,8 +20,9 @@ use crate::libc::LibcDetectionError; use crate::platform::Error as PlatformError; use crate::platform::{Arch, Libc, Os}; use crate::python_version::PythonVersion; -use crate::PythonRequest; +use crate::{PythonRequest, PythonVariant}; use uv_fs::{LockedFile, Simplified}; +use uv_static::EnvVars; #[derive(Error, Debug)] pub enum Error { @@ -80,7 +81,7 @@ impl ManagedPythonInstallations { /// 2. A directory in the system-appropriate user-level data directory, e.g., `~/.local/uv/python` /// 3. A directory in the local data directory, e.g., `./.uv/python` pub fn from_settings() -> Result { - if let Some(install_dir) = std::env::var_os("UV_PYTHON_INSTALL_DIR") { + if let Some(install_dir) = std::env::var_os(EnvVars::UV_PYTHON_INSTALL_DIR) { Ok(Self::from_path(install_dir)) } else { Ok(Self::from_path( @@ -329,13 +330,17 @@ impl ManagedPythonInstallation { let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) { self.python_dir().join("Lib") } else { + let lib_suffix = match self.key.variant { + PythonVariant::Default => "", + PythonVariant::Freethreaded => "t", + }; let python = if matches!( self.key.implementation, LenientImplementationName::Known(ImplementationName::PyPy) ) { format!("pypy{}", self.key.version().python_version()) } else { - format!("python{}", self.key.version().python_version()) + format!("python{}{lib_suffix}", self.key.version().python_version()) }; self.python_dir().join("lib").join(python) }; diff --git a/crates/uv-python/src/microsoft_store.rs b/crates/uv-python/src/microsoft_store.rs index aefdbb2af..c0226733c 100644 --- a/crates/uv-python/src/microsoft_store.rs +++ b/crates/uv-python/src/microsoft_store.rs @@ -10,6 +10,7 @@ use std::env; use std::path::PathBuf; use std::str::FromStr; use tracing::debug; +use uv_static::EnvVars; #[derive(Debug)] struct MicrosoftStorePython { @@ -91,7 +92,7 @@ const MICROSOFT_STORE_PYTHONS: &[MicrosoftStorePython] = &[ /// /// Effectively a port of pub(crate) fn find_microsoft_store_pythons() -> impl Iterator { - let Ok(local_app_data) = env::var("LOCALAPPDATA") else { + let Ok(local_app_data) = env::var(EnvVars::LOCALAPPDATA) else { debug!("`LOCALAPPDATA` not set, ignoring Microsoft store Pythons"); return Either::Left(std::iter::empty()); }; diff --git a/crates/uv-python/src/platform.rs b/crates/uv-python/src/platform.rs index be55f22a9..fd6eb08ce 100644 --- a/crates/uv-python/src/platform.rs +++ b/crates/uv-python/src/platform.rs @@ -46,6 +46,8 @@ impl FromStr for Libc { fn from_str(s: &str) -> Result { match s { "gnu" => Ok(Self::Some(target_lexicon::Environment::Gnu)), + "gnueabi" => Ok(Self::Some(target_lexicon::Environment::Gnueabi)), + "gnueabihf" => Ok(Self::Some(target_lexicon::Environment::Gnueabihf)), "musl" => Ok(Self::Some(target_lexicon::Environment::Musl)), "none" => Ok(Self::None), _ => Err(Error::UnknownLibc(s.to_string())), diff --git a/crates/uv-python/src/python_version.rs b/crates/uv-python/src/python_version.rs index b64f6ba6f..0e5920b05 100644 --- a/crates/uv-python/src/python_version.rs +++ b/crates/uv-python/src/python_version.rs @@ -168,37 +168,4 @@ impl PythonVersion { } #[cfg(test)] -mod tests { - use std::str::FromStr; - - use uv_pep440::{Prerelease, PrereleaseKind, Version}; - - use crate::PythonVersion; - - #[test] - fn python_markers() { - let version = PythonVersion::from_str("3.11.0").expect("valid python version"); - assert_eq!(version.python_version(), Version::new([3, 11])); - assert_eq!(version.python_version().to_string(), "3.11"); - assert_eq!(version.python_full_version(), Version::new([3, 11, 0])); - assert_eq!(version.python_full_version().to_string(), "3.11.0"); - - let version = PythonVersion::from_str("3.11").expect("valid python version"); - assert_eq!(version.python_version(), Version::new([3, 11])); - assert_eq!(version.python_version().to_string(), "3.11"); - assert_eq!(version.python_full_version(), Version::new([3, 11, 0])); - assert_eq!(version.python_full_version().to_string(), "3.11.0"); - - let version = PythonVersion::from_str("3.11.8a1").expect("valid python version"); - assert_eq!(version.python_version(), Version::new([3, 11])); - assert_eq!(version.python_version().to_string(), "3.11"); - assert_eq!( - version.python_full_version(), - Version::new([3, 11, 8]).with_pre(Some(Prerelease { - kind: PrereleaseKind::Alpha, - number: 1 - })) - ); - assert_eq!(version.python_full_version().to_string(), "3.11.8a1"); - } -} +mod tests; diff --git a/crates/uv-python/src/python_version/tests.rs b/crates/uv-python/src/python_version/tests.rs new file mode 100644 index 000000000..7065b1ae0 --- /dev/null +++ b/crates/uv-python/src/python_version/tests.rs @@ -0,0 +1,32 @@ +use std::str::FromStr; + +use uv_pep440::{Prerelease, PrereleaseKind, Version}; + +use crate::PythonVersion; + +#[test] +fn python_markers() { + let version = PythonVersion::from_str("3.11.0").expect("valid python version"); + assert_eq!(version.python_version(), Version::new([3, 11])); + assert_eq!(version.python_version().to_string(), "3.11"); + assert_eq!(version.python_full_version(), Version::new([3, 11, 0])); + assert_eq!(version.python_full_version().to_string(), "3.11.0"); + + let version = PythonVersion::from_str("3.11").expect("valid python version"); + assert_eq!(version.python_version(), Version::new([3, 11])); + assert_eq!(version.python_version().to_string(), "3.11"); + assert_eq!(version.python_full_version(), Version::new([3, 11, 0])); + assert_eq!(version.python_full_version().to_string(), "3.11.0"); + + let version = PythonVersion::from_str("3.11.8a1").expect("valid python version"); + assert_eq!(version.python_version(), Version::new([3, 11])); + assert_eq!(version.python_version().to_string(), "3.11"); + assert_eq!( + version.python_full_version(), + Version::new([3, 11, 8]).with_pre(Some(Prerelease { + kind: PrereleaseKind::Alpha, + number: 1 + })) + ); + assert_eq!(version.python_full_version().to_string(), "3.11.8a1"); +} diff --git a/crates/uv-python/src/tests.rs b/crates/uv-python/src/tests.rs new file mode 100644 index 000000000..ad5f2d495 --- /dev/null +++ b/crates/uv-python/src/tests.rs @@ -0,0 +1,2266 @@ +use std::{ + env, + ffi::{OsStr, OsString}, + path::{Path, PathBuf}, + str::FromStr, +}; + +use anyhow::Result; +use assert_fs::{fixture::ChildPath, prelude::*, TempDir}; +use indoc::{formatdoc, indoc}; +use temp_env::with_vars; +use test_log::test; +use uv_static::EnvVars; + +use uv_cache::Cache; + +use crate::{ + discovery::{find_best_python_installation, find_python_installation, EnvironmentPreference}, + PythonPreference, +}; +use crate::{ + implementation::ImplementationName, installation::PythonInstallation, + managed::ManagedPythonInstallations, virtualenv::virtualenv_python_executable, PythonNotFound, + PythonRequest, PythonSource, PythonVersion, +}; + +struct TestContext { + tempdir: TempDir, + cache: Cache, + installations: ManagedPythonInstallations, + search_path: Option>, + workdir: ChildPath, +} + +impl TestContext { + fn new() -> Result { + let tempdir = TempDir::new()?; + let workdir = tempdir.child("workdir"); + workdir.create_dir_all()?; + + Ok(Self { + tempdir, + cache: Cache::temp()?, + installations: ManagedPythonInstallations::temp()?, + search_path: None, + workdir, + }) + } + + /// Clear the search path. + fn reset_search_path(&mut self) { + self.search_path = None; + } + + /// Add a directory to the search path. + fn add_to_search_path(&mut self, path: PathBuf) { + match self.search_path.as_mut() { + Some(paths) => paths.push(path), + None => self.search_path = Some(vec![path]), + }; + } + + /// Create a new directory and add it to the search path. + fn new_search_path_directory(&mut self, name: impl AsRef) -> Result { + let child = self.tempdir.child(name); + child.create_dir_all()?; + self.add_to_search_path(child.to_path_buf()); + Ok(child) + } + + fn run(&self, closure: F) -> R + where + F: FnOnce() -> R, + { + self.run_with_vars(&[], closure) + } + + fn run_with_vars(&self, vars: &[(&str, Option<&OsStr>)], closure: F) -> R + where + F: FnOnce() -> R, + { + let path = self + .search_path + .as_ref() + .map(|paths| env::join_paths(paths).unwrap()); + + let mut run_vars = vec![ + // Ensure `PATH` is used + (EnvVars::UV_TEST_PYTHON_PATH, None), + // Ignore active virtual environments (i.e. that the dev is using) + (EnvVars::VIRTUAL_ENV, None), + (EnvVars::PATH, path.as_deref()), + // Use the temporary python directory + ( + EnvVars::UV_PYTHON_INSTALL_DIR, + Some(self.installations.root().as_os_str()), + ), + // Set a working directory + ("PWD", Some(self.workdir.path().as_os_str())), + ]; + for (key, value) in vars { + run_vars.push((key, *value)); + } + with_vars(&run_vars, closure) + } + + /// Create a fake Python interpreter executable which returns fixed metadata mocking our interpreter + /// query script output. + fn create_mock_interpreter( + path: &Path, + version: &PythonVersion, + implementation: ImplementationName, + system: bool, + free_threaded: bool, + ) -> Result<()> { + let json = indoc! {r##" + { + "result": "success", + "platform": { + "os": { + "name": "manylinux", + "major": 2, + "minor": 38 + }, + "arch": "x86_64" + }, + "manylinux_compatible": true, + "markers": { + "implementation_name": "{IMPLEMENTATION}", + "implementation_version": "{FULL_VERSION}", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "{IMPLEMENTATION}", + "platform_release": "6.5.0-13-generic", + "platform_system": "Linux", + "platform_version": "#13-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 3 12:16:05 UTC 2023", + "python_full_version": "{FULL_VERSION}", + "python_version": "{VERSION}", + "sys_platform": "linux" + }, + "sys_base_exec_prefix": "/home/ferris/.pyenv/versions/{FULL_VERSION}", + "sys_base_prefix": "/home/ferris/.pyenv/versions/{FULL_VERSION}", + "sys_prefix": "{PREFIX}", + "sys_executable": "{PATH}", + "sys_path": [ + "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}/lib/python{VERSION}", + "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}/site-packages" + ], + "stdlib": "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}", + "scheme": { + "data": "/home/ferris/.pyenv/versions/{FULL_VERSION}", + "include": "/home/ferris/.pyenv/versions/{FULL_VERSION}/include", + "platlib": "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}/site-packages", + "purelib": "/home/ferris/.pyenv/versions/{FULL_VERSION}/lib/python{VERSION}/site-packages", + "scripts": "/home/ferris/.pyenv/versions/{FULL_VERSION}/bin" + }, + "virtualenv": { + "data": "", + "include": "include", + "platlib": "lib/python{VERSION}/site-packages", + "purelib": "lib/python{VERSION}/site-packages", + "scripts": "bin" + }, + "pointer_size": "64", + "gil_disabled": {FREE_THREADED} + } + "##}; + + let json = if system { + json.replace("{PREFIX}", "/home/ferris/.pyenv/versions/{FULL_VERSION}") + } else { + json.replace("{PREFIX}", "/home/ferris/projects/uv/.venv") + }; + + let json = json + .replace( + "{PATH}", + path.to_str().expect("Path can be represented as string"), + ) + .replace("{FULL_VERSION}", &version.to_string()) + .replace("{VERSION}", &version.without_patch().to_string()) + .replace("{FREE_THREADED}", &free_threaded.to_string()) + .replace("{IMPLEMENTATION}", (&implementation).into()); + + fs_err::create_dir_all(path.parent().unwrap())?; + fs_err::write( + path, + formatdoc! {r##" + #!/bin/bash + echo '{json}' + "##}, + )?; + + fs_err::set_permissions(path, std::os::unix::fs::PermissionsExt::from_mode(0o770))?; + + Ok(()) + } + + /// Create a mock Python 2 interpreter executable which returns a fixed error message mocking + /// invocation of Python 2 with the `-I` flag as done by our query script. + fn create_mock_python2_interpreter(path: &Path) -> Result<()> { + let output = indoc! { r" + Unknown option: -I + usage: /usr/bin/python [option] ... [-c cmd | -m mod | file | -] [arg] ... + Try `python -h` for more information. + "}; + + fs_err::write( + path, + formatdoc! {r##" + #!/bin/bash + echo '{output}' 1>&2 + "##}, + )?; + + fs_err::set_permissions(path, std::os::unix::fs::PermissionsExt::from_mode(0o770))?; + + Ok(()) + } + + /// Create child directories in a temporary directory. + fn new_search_path_directories( + &mut self, + names: &[impl AsRef], + ) -> Result> { + let paths = names + .iter() + .map(|name| self.new_search_path_directory(name)) + .collect::>>()?; + Ok(paths) + } + + /// Create fake Python interpreters the given Python versions. + /// + /// Adds them to the test context search path. + fn add_python_to_workdir(&self, name: &str, version: &str) -> Result<()> { + TestContext::create_mock_interpreter( + self.workdir.child(name).as_ref(), + &PythonVersion::from_str(version).expect("Test uses valid version"), + ImplementationName::default(), + true, + false, + ) + } + + /// Create fake Python interpreters the given Python versions. + /// + /// Adds them to the test context search path. + fn add_python_versions(&mut self, versions: &[&'static str]) -> Result<()> { + let interpreters: Vec<_> = versions + .iter() + .map(|version| (true, ImplementationName::default(), "python", *version)) + .collect(); + self.add_python_interpreters(interpreters.as_slice()) + } + + /// Create fake Python interpreters the given Python implementations and versions. + /// + /// Adds them to the test context search path. + fn add_python_interpreters( + &mut self, + kinds: &[(bool, ImplementationName, &'static str, &'static str)], + ) -> Result<()> { + // Generate a "unique" folder name for each interpreter + let names: Vec = kinds + .iter() + .map(|(system, implementation, name, version)| { + OsString::from_str(&format!("{system}-{implementation}-{name}-{version}")).unwrap() + }) + .collect(); + let paths = self.new_search_path_directories(names.as_slice())?; + for (path, (system, implementation, executable, version)) in + itertools::zip_eq(&paths, kinds) + { + let python = format!("{executable}{}", env::consts::EXE_SUFFIX); + Self::create_mock_interpreter( + &path.join(python), + &PythonVersion::from_str(version).unwrap(), + *implementation, + *system, + false, + )?; + } + Ok(()) + } + + /// Create a mock virtual environment at the given directory + fn mock_venv(path: impl AsRef, version: &'static str) -> Result<()> { + let executable = virtualenv_python_executable(path.as_ref()); + fs_err::create_dir_all( + executable + .parent() + .expect("A Python executable path should always have a parent"), + )?; + TestContext::create_mock_interpreter( + &executable, + &PythonVersion::from_str(version).expect("A valid Python version is used for tests"), + ImplementationName::default(), + false, + false, + )?; + ChildPath::new(path.as_ref().join("pyvenv.cfg")).touch()?; + Ok(()) + } + + /// Create a mock conda prefix at the given directory. + /// + /// These are like virtual environments but they look like system interpreters because `prefix` and `base_prefix` are equal. + fn mock_conda_prefix(path: impl AsRef, version: &'static str) -> Result<()> { + let executable = virtualenv_python_executable(&path); + fs_err::create_dir_all( + executable + .parent() + .expect("A Python executable path should always have a parent"), + )?; + TestContext::create_mock_interpreter( + &executable, + &PythonVersion::from_str(version).expect("A valid Python version is used for tests"), + ImplementationName::default(), + true, + false, + )?; + ChildPath::new(path.as_ref().join("pyvenv.cfg")).touch()?; + Ok(()) + } +} + +#[test] +fn find_python_empty_path() -> Result<()> { + let mut context = TestContext::new()?; + + context.search_path = Some(vec![]); + let result = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::default(), + &context.cache, + ) + }); + assert!( + matches!(result, Ok(Err(PythonNotFound { .. }))), + "With an empty path, no Python installation should be detected got {result:?}" + ); + + context.search_path = None; + let result = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::default(), + &context.cache, + ) + }); + assert!( + matches!(result, Ok(Err(PythonNotFound { .. }))), + "With an unset path, no Python installation should be detected got {result:?}" + ); + + Ok(()) +} + +#[test] +fn find_python_unexecutable_file() -> Result<()> { + let mut context = TestContext::new()?; + context + .new_search_path_directory("path")? + .child(format!("python{}", env::consts::EXE_SUFFIX)) + .touch()?; + + let result = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::default(), + &context.cache, + ) + }); + assert!( + matches!(result, Ok(Err(PythonNotFound { .. }))), + "With an non-executable Python, no Python installation should be detected; got {result:?}" + ); + + Ok(()) +} + +#[test] +fn find_python_valid_executable() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.12.1"])?; + + let interpreter = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::default(), + &context.cache, + ) + })??; + assert!( + matches!( + interpreter, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should find the valid executable; got {interpreter:?}" + ); + + Ok(()) +} + +#[test] +fn find_python_valid_executable_after_invalid() -> Result<()> { + let mut context = TestContext::new()?; + let children = context.new_search_path_directories(&[ + "query-parse-error", + "not-executable", + "empty", + "good", + ])?; + + // An executable file with a bad response + #[cfg(unix)] + fs_err::write( + children[0].join(format!("python{}", env::consts::EXE_SUFFIX)), + formatdoc! {r##" + #!/bin/bash + echo 'foo' + "##}, + )?; + fs_err::set_permissions( + children[0].join(format!("python{}", env::consts::EXE_SUFFIX)), + std::os::unix::fs::PermissionsExt::from_mode(0o770), + )?; + + // A non-executable file + ChildPath::new(children[1].join(format!("python{}", env::consts::EXE_SUFFIX))).touch()?; + + // An empty directory at `children[2]` + + // An good interpreter! + let python_path = children[3].join(format!("python{}", env::consts::EXE_SUFFIX)); + TestContext::create_mock_interpreter( + &python_path, + &PythonVersion::from_str("3.12.1").unwrap(), + ImplementationName::default(), + true, + false, + )?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::default(), + &context.cache, + ) + })??; + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should skip the bad executables in favor of the good one; got {python:?}" + ); + assert_eq!(python.interpreter().sys_executable(), python_path); + + Ok(()) +} + +#[test] +fn find_python_only_python2_executable() -> Result<()> { + let mut context = TestContext::new()?; + let python = context + .new_search_path_directory("python2")? + .child(format!("python{}", env::consts::EXE_SUFFIX)); + TestContext::create_mock_python2_interpreter(&python)?; + + let result = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::default(), + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + // TODO(zanieb): We could improve the error handling to hint this to the user + "If only Python 2 is available, we should not find a python; got {result:?}" + ); + + Ok(()) +} + +#[test] +fn find_python_skip_python2_executable() -> Result<()> { + let mut context = TestContext::new()?; + + let python2 = context + .new_search_path_directory("python2")? + .child(format!("python{}", env::consts::EXE_SUFFIX)); + TestContext::create_mock_python2_interpreter(&python2)?; + + let python3 = context + .new_search_path_directory("python3")? + .child(format!("python{}", env::consts::EXE_SUFFIX)); + TestContext::create_mock_interpreter( + &python3, + &PythonVersion::from_str("3.12.1").unwrap(), + ImplementationName::default(), + true, + false, + )?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::default(), + &context.cache, + ) + })??; + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should skip the Python 2 installation and find the Python 3 interpreter; got {python:?}" + ); + assert_eq!(python.interpreter().sys_executable(), python3.path()); + + Ok(()) +} + +#[test] +fn find_python_system_python_allowed() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (false, ImplementationName::CPython, "python", "3.10.0"), + (true, ImplementationName::CPython, "python", "3.10.1"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "Should find the first interpreter regardless of system" + ); + + // Reverse the order of the virtual environment and system + context.reset_search_path(); + context.add_python_interpreters(&[ + (true, ImplementationName::CPython, "python", "3.10.1"), + (false, ImplementationName::CPython, "python", "3.10.0"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "Should find the first interpreter regardless of system" + ); + + Ok(()) +} + +#[test] +fn find_python_system_python_required() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (false, ImplementationName::CPython, "python", "3.10.0"), + (true, ImplementationName::CPython, "python", "3.10.1"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "Should skip the virtual environment" + ); + + Ok(()) +} + +#[test] +fn find_python_system_python_disallowed() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (true, ImplementationName::CPython, "python", "3.10.0"), + (false, ImplementationName::CPython, "python", "3.10.1"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "Should skip the system Python" + ); + + Ok(()) +} + +#[test] +fn find_python_version_minor() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("3.11"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should find a python; got {python:?}" + ); + assert_eq!( + &python.interpreter().python_full_version().to_string(), + "3.11.2", + "We should find the correct interpreter for the request" + ); + + Ok(()) +} + +#[test] +fn find_python_version_patch() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.10.1", "3.11.3", "3.11.2", "3.12.3"])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("3.11.2"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should find a python; got {python:?}" + ); + assert_eq!( + &python.interpreter().python_full_version().to_string(), + "3.11.2", + "We should find the correct interpreter for the request" + ); + + Ok(()) +} + +#[test] +fn find_python_version_minor_no_match() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; + + let result = context.run(|| { + find_python_installation( + &PythonRequest::parse("3.9"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not find a python; got {result:?}" + ); + + Ok(()) +} + +#[test] +fn find_python_version_patch_no_match() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.10.1", "3.11.2", "3.12.3"])?; + + let result = context.run(|| { + find_python_installation( + &PythonRequest::parse("3.11.9"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not find a python; got {result:?}" + ); + + Ok(()) +} + +#[test] +fn find_best_python_version_patch_exact() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.10.1", "3.11.2", "3.11.4", "3.11.3", "3.12.5"])?; + + let python = context.run(|| { + find_best_python_installation( + &PythonRequest::parse("3.11.3"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should find a python; got {python:?}" + ); + assert_eq!( + &python.interpreter().python_full_version().to_string(), + "3.11.3", + "We should prefer the exact request" + ); + + Ok(()) +} + +#[test] +fn find_best_python_version_patch_fallback() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.10.1", "3.11.2", "3.11.4", "3.11.3", "3.12.5"])?; + + let python = context.run(|| { + find_best_python_installation( + &PythonRequest::parse("3.11.11"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should find a python; got {python:?}" + ); + assert_eq!( + &python.interpreter().python_full_version().to_string(), + "3.11.2", + "We should fallback to the first matching minor" + ); + + Ok(()) +} + +#[test] +fn find_best_python_skips_source_without_match() -> Result<()> { + let mut context = TestContext::new()?; + let venv = context.tempdir.child(".venv"); + TestContext::mock_venv(&venv, "3.12.0")?; + context.add_python_versions(&["3.10.1"])?; + + let python = + context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || { + find_best_python_installation( + &PythonRequest::parse("3.10"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should skip the active environment in favor of the requested version; got {python:?}" + ); + + Ok(()) +} + +#[test] +fn find_best_python_returns_to_earlier_source_on_fallback() -> Result<()> { + let mut context = TestContext::new()?; + let venv = context.tempdir.child(".venv"); + TestContext::mock_venv(&venv, "3.10.1")?; + context.add_python_versions(&["3.10.3"])?; + + let python = + context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || { + find_best_python_installation( + &PythonRequest::parse("3.10.2"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::ActiveEnvironment, + interpreter: _ + } + ), + "We should prefer the active environment after relaxing; got {python:?}" + ); + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should prefer the active environment" + ); + + Ok(()) +} + +#[test] +fn find_python_from_active_python() -> Result<()> { + let context = TestContext::new()?; + let venv = context.tempdir.child("some-venv"); + TestContext::mock_venv(&venv, "3.12.0")?; + + let python = + context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should prefer the active environment" + ); + + Ok(()) +} + +#[test] +fn find_python_from_active_python_prerelease() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.12.0"])?; + let venv = context.tempdir.child("some-venv"); + TestContext::mock_venv(&venv, "3.13.0rc1")?; + + let python = + context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.13.0rc1", + "We should prefer the active environment" + ); + + Ok(()) +} + +#[test] +fn find_python_from_conda_prefix() -> Result<()> { + let context = TestContext::new()?; + let condaenv = context.tempdir.child("condaenv"); + TestContext::mock_conda_prefix(&condaenv, "3.12.0")?; + + let python = context.run_with_vars( + &[(EnvVars::CONDA_PREFIX, Some(condaenv.as_os_str()))], + || { + // Note this python is not treated as a system interpreter + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlyVirtual, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should allow the active conda python" + ); + + Ok(()) +} + +#[test] +fn find_python_from_conda_prefix_and_virtualenv() -> Result<()> { + let context = TestContext::new()?; + let venv = context.tempdir.child(".venv"); + TestContext::mock_venv(&venv, "3.12.0")?; + let condaenv = context.tempdir.child("condaenv"); + TestContext::mock_conda_prefix(&condaenv, "3.12.1")?; + + let python = context.run_with_vars( + &[ + (EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())), + (EnvVars::CONDA_PREFIX, Some(condaenv.as_os_str())), + ], + || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should prefer the non-conda python" + ); + + // Put a virtual environment in the working directory + let venv = context.workdir.child(".venv"); + TestContext::mock_venv(venv, "3.12.2")?; + let python = context.run_with_vars( + &[(EnvVars::CONDA_PREFIX, Some(condaenv.as_os_str()))], + || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.1", + "We should prefer the conda python over inactive virtual environments" + ); + + Ok(()) +} + +#[test] +fn find_python_from_discovered_python() -> Result<()> { + let mut context = TestContext::new()?; + + // Create a virtual environment in a parent of the workdir + let venv = context.tempdir.child(".venv"); + TestContext::mock_venv(venv, "3.12.0")?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should find the python" + ); + + // Add some system versions to ensure we don't use those + context.add_python_versions(&["3.12.1", "3.12.2"])?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should prefer the discovered virtual environment over available system versions" + ); + + Ok(()) +} + +#[test] +fn find_python_skips_broken_active_python() -> Result<()> { + let context = TestContext::new()?; + let venv = context.tempdir.child(".venv"); + TestContext::mock_venv(&venv, "3.12.0")?; + + // Delete the pyvenv cfg to break the virtualenv + fs_err::remove_file(venv.join("pyvenv.cfg"))?; + + let python = + context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + // TODO(zanieb): We should skip this python, why don't we? + "We should prefer the active environment" + ); + + Ok(()) +} + +#[test] +fn find_python_from_parent_interpreter() -> Result<()> { + let mut context = TestContext::new()?; + + let parent = context.tempdir.child("python").to_path_buf(); + TestContext::create_mock_interpreter( + &parent, + &PythonVersion::from_str("3.12.0").unwrap(), + ImplementationName::CPython, + // Note we mark this as a system interpreter instead of a virtual environment + true, + false, + )?; + + let python = context.run_with_vars( + &[( + EnvVars::UV_INTERNAL__PARENT_INTERPRETER, + Some(parent.as_os_str()), + )], + || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should find the parent interpreter" + ); + + // Parent interpreters are preferred over virtual environments and system interpreters + let venv = context.tempdir.child(".venv"); + TestContext::mock_venv(&venv, "3.12.2")?; + context.add_python_versions(&["3.12.3"])?; + let python = context.run_with_vars( + &[ + ( + EnvVars::UV_INTERNAL__PARENT_INTERPRETER, + Some(parent.as_os_str()), + ), + (EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())), + ], + || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should prefer the parent interpreter" + ); + + // Test with `EnvironmentPreference::ExplicitSystem` + let python = context.run_with_vars( + &[ + ( + EnvVars::UV_INTERNAL__PARENT_INTERPRETER, + Some(parent.as_os_str()), + ), + (EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())), + ], + || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::ExplicitSystem, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should prefer the parent interpreter" + ); + + // Test with `EnvironmentPreference::OnlySystem` + let python = context.run_with_vars( + &[ + ( + EnvVars::UV_INTERNAL__PARENT_INTERPRETER, + Some(parent.as_os_str()), + ), + (EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())), + ], + || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should prefer the parent interpreter since it's not virtual" + ); + + // Test with `EnvironmentPreference::OnlyVirtual` + let python = context.run_with_vars( + &[ + ( + EnvVars::UV_INTERNAL__PARENT_INTERPRETER, + Some(parent.as_os_str()), + ), + (EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())), + ], + || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlyVirtual, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.2", + "We find the virtual environment Python because a system is explicitly not allowed" + ); + + Ok(()) +} + +#[test] +fn find_python_from_parent_interpreter_prerelease() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.12.0"])?; + let parent = context.tempdir.child("python").to_path_buf(); + TestContext::create_mock_interpreter( + &parent, + &PythonVersion::from_str("3.13.0rc2").unwrap(), + ImplementationName::CPython, + // Note we mark this as a system interpreter instead of a virtual environment + true, + false, + )?; + + let python = context.run_with_vars( + &[( + EnvVars::UV_INTERNAL__PARENT_INTERPRETER, + Some(parent.as_os_str()), + )], + || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.13.0rc2", + "We should find the parent interpreter" + ); + + Ok(()) +} + +#[test] +fn find_python_active_python_skipped_if_system_required() -> Result<()> { + let mut context = TestContext::new()?; + let venv = context.tempdir.child(".venv"); + TestContext::mock_venv(&venv, "3.9.0")?; + context.add_python_versions(&["3.10.0", "3.11.1", "3.12.2"])?; + + // Without a specific request + let python = + context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlySystem, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should skip the active environment" + ); + + // With a requested minor version + let python = + context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || { + find_python_installation( + &PythonRequest::parse("3.12"), + EnvironmentPreference::OnlySystem, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.2", + "We should skip the active environment" + ); + + // With a patch version that cannot be python + let result = + context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || { + find_python_installation( + &PythonRequest::parse("3.12.3"), + EnvironmentPreference::OnlySystem, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + result.is_err(), + "We should not find an python; got {result:?}" + ); + + Ok(()) +} + +#[test] +fn find_python_fails_if_no_virtualenv_and_system_not_allowed() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_versions(&["3.10.1", "3.11.2"])?; + + let result = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::OnlyVirtual, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not find an python; got {result:?}" + ); + + // With an invalid virtual environment variable + let result = context.run_with_vars( + &[(EnvVars::VIRTUAL_ENV, Some(context.tempdir.as_os_str()))], + || { + find_python_installation( + &PythonRequest::parse("3.12.3"), + EnvironmentPreference::OnlySystem, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not find an python; got {result:?}" + ); + Ok(()) +} + +#[test] +fn find_python_allows_name_in_working_directory() -> Result<()> { + let context = TestContext::new()?; + context.add_python_to_workdir("foobar", "3.10.0")?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("foobar"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should find the named executable" + ); + + let result = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not find it without a specific request" + ); + + let result = context.run(|| { + find_python_installation( + &PythonRequest::parse("3.10.0"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not find it via a matching version request" + ); + + Ok(()) +} + +#[test] +fn find_python_allows_relative_file_path() -> Result<()> { + let mut context = TestContext::new()?; + let python = context.workdir.child("foo").join("bar"); + TestContext::create_mock_interpreter( + &python, + &PythonVersion::from_str("3.10.0").unwrap(), + ImplementationName::default(), + true, + false, + )?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("./foo/bar"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should find the `bar` executable" + ); + + context.add_python_versions(&["3.11.1"])?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("./foo/bar"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should prefer the `bar` executable over the system and virtualenvs" + ); + + Ok(()) +} + +#[test] +fn find_python_allows_absolute_file_path() -> Result<()> { + let mut context = TestContext::new()?; + let python_path = context.tempdir.child("foo").join("bar"); + TestContext::create_mock_interpreter( + &python_path, + &PythonVersion::from_str("3.10.0").unwrap(), + ImplementationName::default(), + true, + false, + )?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(python_path.to_str().unwrap()), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should find the `bar` executable" + ); + + // With `EnvironmentPreference::ExplicitSystem` + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(python_path.to_str().unwrap()), + EnvironmentPreference::ExplicitSystem, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should allow the `bar` executable with explicit system" + ); + + // With `EnvironmentPreference::OnlyVirtual` + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(python_path.to_str().unwrap()), + EnvironmentPreference::OnlyVirtual, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should allow the `bar` executable and verify it is virtual" + ); + + context.add_python_versions(&["3.11.1"])?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(python_path.to_str().unwrap()), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should prefer the `bar` executable over the system and virtualenvs" + ); + + Ok(()) +} + +#[test] +fn find_python_allows_venv_directory_path() -> Result<()> { + let mut context = TestContext::new()?; + + let venv = context.tempdir.child("foo").child(".venv"); + TestContext::mock_venv(&venv, "3.10.0")?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("../foo/.venv"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should find the relative venv path" + ); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(venv.to_str().unwrap()), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should find the absolute venv path" + ); + + // We should allow it to be a directory that _looks_ like a virtual environment. + let python_path = context.tempdir.child("bar").join("bin").join("python"); + TestContext::create_mock_interpreter( + &python_path, + &PythonVersion::from_str("3.10.0").unwrap(), + ImplementationName::default(), + true, + false, + )?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(context.tempdir.child("bar").to_str().unwrap()), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should find the executable in the directory" + ); + + let other_venv = context.tempdir.child("foobar").child(".venv"); + TestContext::mock_venv(&other_venv, "3.11.1")?; + context.add_python_versions(&["3.12.2"])?; + let python = context.run_with_vars( + &[(EnvVars::VIRTUAL_ENV, Some(other_venv.as_os_str()))], + || { + find_python_installation( + &PythonRequest::parse(venv.to_str().unwrap()), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + }, + )??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should prefer the requested directory over the system and active virtual environments" + ); + + Ok(()) +} + +#[test] +fn find_python_venv_symlink() -> Result<()> { + let context = TestContext::new()?; + + let venv = context.tempdir.child("target").child("env"); + TestContext::mock_venv(&venv, "3.10.6")?; + let symlink = context.tempdir.child("proj").child(".venv"); + context.tempdir.child("proj").create_dir_all()?; + symlink.symlink_to_dir(venv)?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("../proj/.venv"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.6", + "We should find the symlinked venv" + ); + Ok(()) +} + +#[test] +fn find_python_treats_missing_file_path_as_file() -> Result<()> { + let context = TestContext::new()?; + context.workdir.child("foo").create_dir_all()?; + + let result = context.run(|| { + find_python_installation( + &PythonRequest::parse("./foo/bar"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not find the file; got {result:?}" + ); + + Ok(()) +} + +#[test] +fn find_python_executable_name_in_search_path() -> Result<()> { + let mut context = TestContext::new()?; + let python = context.tempdir.child("foo").join("bar"); + TestContext::create_mock_interpreter( + &python, + &PythonVersion::from_str("3.10.0").unwrap(), + ImplementationName::default(), + true, + false, + )?; + context.add_to_search_path(context.tempdir.child("foo").to_path_buf()); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("bar"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should find the `bar` executable" + ); + + // With [`EnvironmentPreference::OnlyVirtual`], we should not allow the interpreter + let result = context.run(|| { + find_python_installation( + &PythonRequest::parse("bar"), + EnvironmentPreference::ExplicitSystem, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not allow a system interpreter; got {result:?}" + ); + + // Unless it's a virtual environment interpreter + let mut context = TestContext::new()?; + let python = context.tempdir.child("foo").join("bar"); + TestContext::create_mock_interpreter( + &python, + &PythonVersion::from_str("3.10.0").unwrap(), + ImplementationName::default(), + false, // Not a system interpreter + false, + )?; + context.add_to_search_path(context.tempdir.child("foo").to_path_buf()); + + let python = context + .run(|| { + find_python_installation( + &PythonRequest::parse("bar"), + EnvironmentPreference::ExplicitSystem, + PythonPreference::OnlySystem, + &context.cache, + ) + }) + .unwrap() + .unwrap(); + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should find the `bar` executable" + ); + + Ok(()) +} + +#[test] +fn find_python_pypy() -> Result<()> { + let mut context = TestContext::new()?; + + context.add_python_interpreters(&[(true, ImplementationName::PyPy, "pypy", "3.10.0")])?; + let result = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not find the pypy interpreter if not named `python` or requested; got {result:?}" + ); + + // But we should find it + context.reset_search_path(); + context.add_python_interpreters(&[(true, ImplementationName::PyPy, "python", "3.10.1")])?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should find the pypy interpreter if it's the only one" + ); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("pypy"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should find the pypy interpreter if it's requested" + ); + + Ok(()) +} + +#[test] +fn find_python_pypy_request_ignores_cpython() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (true, ImplementationName::CPython, "python", "3.10.0"), + (true, ImplementationName::PyPy, "pypy", "3.10.1"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("pypy"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should skip the CPython interpreter" + ); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should take the first interpreter without a specific request" + ); + + Ok(()) +} + +#[test] +fn find_python_pypy_request_skips_wrong_versions() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (true, ImplementationName::PyPy, "pypy", "3.9"), + (true, ImplementationName::PyPy, "pypy", "3.10.1"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("pypy3.10"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should skip the first interpreter" + ); + + Ok(()) +} + +#[test] +fn find_python_pypy_finds_executable_with_version_name() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (true, ImplementationName::PyPy, "pypy3.9", "3.10.0"), // We don't consider this one because of the executable name + (true, ImplementationName::PyPy, "pypy3.10", "3.10.1"), + (true, ImplementationName::PyPy, "pypy", "3.10.2"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("pypy@3.10"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should find the requested interpreter version" + ); + + Ok(()) +} + +#[test] +fn find_python_all_minors() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (true, ImplementationName::CPython, "python", "3.10.0"), + (true, ImplementationName::CPython, "python3", "3.10.0"), + (true, ImplementationName::CPython, "python3.12", "3.12.0"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(">= 3.11"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0", + "We should find matching minor version even if they aren't called `python` or `python3`" + ); + + Ok(()) +} + +#[test] +fn find_python_all_minors_prerelease() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (true, ImplementationName::CPython, "python", "3.10.0"), + (true, ImplementationName::CPython, "python3", "3.10.0"), + (true, ImplementationName::CPython, "python3.11", "3.11.0b0"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(">= 3.11"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.11.0b0", + "We should find the 3.11 prerelease even though >=3.11 would normally exclude prereleases" + ); + + Ok(()) +} + +#[test] +fn find_python_all_minors_prerelease_next() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (true, ImplementationName::CPython, "python", "3.10.0"), + (true, ImplementationName::CPython, "python3", "3.10.0"), + (true, ImplementationName::CPython, "python3.12", "3.12.0b0"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse(">= 3.11"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.12.0b0", + "We should find the 3.12 prerelease" + ); + + Ok(()) +} + +#[test] +fn find_python_graalpy() -> Result<()> { + let mut context = TestContext::new()?; + + context.add_python_interpreters(&[(true, ImplementationName::GraalPy, "graalpy", "3.10.0")])?; + let result = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })?; + assert!( + matches!(result, Err(PythonNotFound { .. })), + "We should not the graalpy interpreter if not named `python` or requested; got {result:?}" + ); + + // But we should find it + context.reset_search_path(); + context.add_python_interpreters(&[(true, ImplementationName::GraalPy, "python", "3.10.1")])?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should find the graalpy interpreter if it's the only one" + ); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("graalpy"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should find the graalpy interpreter if it's requested" + ); + + Ok(()) +} + +#[test] +fn find_python_graalpy_request_ignores_cpython() -> Result<()> { + let mut context = TestContext::new()?; + context.add_python_interpreters(&[ + (true, ImplementationName::CPython, "python", "3.10.0"), + (true, ImplementationName::GraalPy, "graalpy", "3.10.1"), + ])?; + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("graalpy"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should skip the CPython interpreter" + ); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::Default, + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should take the first interpreter without a specific request" + ); + + Ok(()) +} + +#[test] +fn find_python_prefers_generic_executable_over_implementation_name() -> Result<()> { + let mut context = TestContext::new()?; + + // We prefer `python` executables over `graalpy` executables in the same directory + // if they are both GraalPy + TestContext::create_mock_interpreter( + &context.tempdir.join("python"), + &PythonVersion::from_str("3.10.0").unwrap(), + ImplementationName::GraalPy, + true, + false, + )?; + TestContext::create_mock_interpreter( + &context.tempdir.join("graalpy"), + &PythonVersion::from_str("3.10.1").unwrap(), + ImplementationName::GraalPy, + true, + false, + )?; + context.add_to_search_path(context.tempdir.to_path_buf()); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("graalpy@3.10"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + ); + + // And `python` executables earlier in the search path will take precedence + context.reset_search_path(); + context.add_python_interpreters(&[ + (true, ImplementationName::GraalPy, "python", "3.10.2"), + (true, ImplementationName::GraalPy, "graalpy", "3.10.3"), + ])?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("graalpy@3.10"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.2", + ); + + // But `graalpy` executables earlier in the search path will take precedence + context.reset_search_path(); + context.add_python_interpreters(&[ + (true, ImplementationName::GraalPy, "graalpy", "3.10.3"), + (true, ImplementationName::GraalPy, "python", "3.10.2"), + ])?; + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("graalpy@3.10"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.3", + ); + + Ok(()) +} + +#[test] +fn find_python_prefers_generic_executable_over_one_with_version() -> Result<()> { + let mut context = TestContext::new()?; + TestContext::create_mock_interpreter( + &context.tempdir.join("pypy3.10"), + &PythonVersion::from_str("3.10.0").unwrap(), + ImplementationName::PyPy, + true, + false, + )?; + TestContext::create_mock_interpreter( + &context.tempdir.join("pypy"), + &PythonVersion::from_str("3.10.1").unwrap(), + ImplementationName::PyPy, + true, + false, + )?; + context.add_to_search_path(context.tempdir.to_path_buf()); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("pypy@3.10"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.1", + "We should prefer the generic executable over one with the version number" + ); + + let mut context = TestContext::new()?; + TestContext::create_mock_interpreter( + &context.tempdir.join("python3.10"), + &PythonVersion::from_str("3.10.0").unwrap(), + ImplementationName::PyPy, + true, + false, + )?; + TestContext::create_mock_interpreter( + &context.tempdir.join("pypy"), + &PythonVersion::from_str("3.10.1").unwrap(), + ImplementationName::PyPy, + true, + false, + )?; + context.add_to_search_path(context.tempdir.to_path_buf()); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("pypy@3.10"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + assert_eq!( + python.interpreter().python_full_version().to_string(), + "3.10.0", + "We should prefer the generic name with a version over one the implementation name" + ); + + Ok(()) +} + +#[test] +fn find_python_version_free_threaded() -> Result<()> { + let mut context = TestContext::new()?; + + TestContext::create_mock_interpreter( + &context.tempdir.join("python"), + &PythonVersion::from_str("3.13.1").unwrap(), + ImplementationName::CPython, + true, + false, + )?; + TestContext::create_mock_interpreter( + &context.tempdir.join("python3.13t"), + &PythonVersion::from_str("3.13.0").unwrap(), + ImplementationName::CPython, + true, + true, + )?; + context.add_to_search_path(context.tempdir.to_path_buf()); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("3.13t"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should find a python; got {python:?}" + ); + assert_eq!( + &python.interpreter().python_full_version().to_string(), + "3.13.0", + "We should find the correct interpreter for the request" + ); + assert!( + &python.interpreter().gil_disabled(), + "We should find a python without the GIL" + ); + + Ok(()) +} + +#[test] +fn find_python_version_prefer_non_free_threaded() -> Result<()> { + let mut context = TestContext::new()?; + + TestContext::create_mock_interpreter( + &context.tempdir.join("python"), + &PythonVersion::from_str("3.13.0").unwrap(), + ImplementationName::CPython, + true, + false, + )?; + TestContext::create_mock_interpreter( + &context.tempdir.join("python3.13t"), + &PythonVersion::from_str("3.13.0").unwrap(), + ImplementationName::CPython, + true, + true, + )?; + context.add_to_search_path(context.tempdir.to_path_buf()); + + let python = context.run(|| { + find_python_installation( + &PythonRequest::parse("3.13"), + EnvironmentPreference::Any, + PythonPreference::OnlySystem, + &context.cache, + ) + })??; + + assert!( + matches!( + python, + PythonInstallation { + source: PythonSource::SearchPath, + interpreter: _ + } + ), + "We should find a python; got {python:?}" + ); + assert_eq!( + &python.interpreter().python_full_version().to_string(), + "3.13.0", + "We should find the correct interpreter for the request" + ); + assert!( + !&python.interpreter().gil_disabled(), + "We should prefer a python with the GIL" + ); + + Ok(()) +} diff --git a/crates/uv-python/src/virtualenv.rs b/crates/uv-python/src/virtualenv.rs index 6fcfac188..865aabf20 100644 --- a/crates/uv-python/src/virtualenv.rs +++ b/crates/uv-python/src/virtualenv.rs @@ -6,6 +6,7 @@ use std::{ use fs_err as fs; use thiserror::Error; use uv_pypi_types::Scheme; +use uv_static::EnvVars; /// The layout of a virtual environment. #[derive(Debug)] @@ -39,9 +40,9 @@ pub struct PyVenvConfiguration { pub enum Error { #[error(transparent)] Io(#[from] io::Error), - #[error("Broken virtualenv `{0}`: `pyvenv.cfg` is missing")] + #[error("Broken virtual environment `{0}`: `pyvenv.cfg` is missing")] MissingPyVenvCfg(PathBuf), - #[error("Broken virtualenv `{0}`: `pyvenv.cfg` could not be parsed")] + #[error("Broken virtual environment `{0}`: `pyvenv.cfg` could not be parsed")] ParsePyVenvCfg(PathBuf, #[source] io::Error), } @@ -49,7 +50,7 @@ pub enum Error { /// /// Supports `VIRTUAL_ENV`. pub(crate) fn virtualenv_from_env() -> Option { - if let Some(dir) = env::var_os("VIRTUAL_ENV").filter(|value| !value.is_empty()) { + if let Some(dir) = env::var_os(EnvVars::VIRTUAL_ENV).filter(|value| !value.is_empty()) { return Some(PathBuf::from(dir)); } @@ -60,7 +61,7 @@ pub(crate) fn virtualenv_from_env() -> Option { /// /// Supports `CONDA_PREFIX`. pub(crate) fn conda_prefix_from_env() -> Option { - if let Some(dir) = env::var_os("CONDA_PREFIX").filter(|value| !value.is_empty()) { + if let Some(dir) = env::var_os(EnvVars::CONDA_PREFIX).filter(|value| !value.is_empty()) { return Some(PathBuf::from(dir)); } diff --git a/crates/uv-python/template-download-metadata.py b/crates/uv-python/template-download-metadata.py index 948c2abf5..536efd459 100755 --- a/crates/uv-python/template-download-metadata.py +++ b/crates/uv-python/template-download-metadata.py @@ -17,6 +17,7 @@ Usage: import argparse import json import logging +import re import subprocess import sys from pathlib import Path @@ -29,6 +30,7 @@ WORKSPACE_ROOT = CRATE_ROOT.parent.parent VERSION_METADATA = CRATE_ROOT / "download-metadata.json" TEMPLATE = CRATE_ROOT / "src" / "downloads.inc.mustache" TARGET = TEMPLATE.with_suffix("") +PRERELEASE_PATTERN = re.compile(r"(a|b|rc)(\d+)") def prepare_name(name: str) -> str: @@ -48,6 +50,18 @@ def prepare_libc(libc: str) -> str | None: return libc.title() +def prepare_variant(variant: str | None) -> str | None: + match variant: + case None: + return "PythonVariant::Default" + case "freethreaded": + return "PythonVariant::Freethreaded" + case "debug": + return "PythonVariant::Debug" + case _: + raise ValueError(f"Unknown variant: {variant}") + + def prepare_arch(arch: str) -> str: match arch: # Special constructors @@ -61,11 +75,22 @@ def prepare_arch(arch: str) -> str: return arch.capitalize() +def prepare_prerelease(prerelease: str) -> str: + if not prerelease: + return "None" + if not (match := PRERELEASE_PATTERN.match(prerelease)): + raise ValueError(f"Invalid prerelease: {prerelease!r}") + kind, number = match.groups() + return f"Some(Prerelease {{ kind: PrereleaseKind::{kind.capitalize()}, number: {number} }})" + + def prepare_value(value: dict) -> dict: value["os"] = value["os"].title() value["arch"] = prepare_arch(value["arch"]) value["name"] = prepare_name(value["name"]) value["libc"] = prepare_libc(value["libc"]) + value["prerelease"] = prepare_prerelease(value["prerelease"]) + value["variant"] = prepare_variant(value["variant"]) return value @@ -78,6 +103,8 @@ def main() -> None: data["versions"] = [ {"key": key, "value": prepare_value(value)} for key, value in json.loads(VERSION_METADATA.read_text()).items() + # Exclude debug variants for now, we don't support them in the Rust side + if value["variant"] != "debug" ] # Render the template @@ -88,7 +115,7 @@ def main() -> None: # Update the file logging.info(f"Updating `{TARGET}`...") - TARGET.write_text(output) + TARGET.write_text("// DO NOT EDIT\n//\n" + output) subprocess.check_call( ["rustfmt", str(TARGET)], stderr=subprocess.STDOUT, diff --git a/crates/uv-requirements-txt/Cargo.toml b/crates/uv-requirements-txt/Cargo.toml index 86d72cf0b..8ab3cb6b3 100644 --- a/crates/uv-requirements-txt/Cargo.toml +++ b/crates/uv-requirements-txt/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-requirements/Cargo.toml b/crates/uv-requirements/Cargo.toml index 2bff81ff5..d3fbf929b 100644 --- a/crates/uv-requirements/Cargo.toml +++ b/crates/uv-requirements/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true authors.workspace = true license.workspace = true +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-requirements/src/extras.rs b/crates/uv-requirements/src/extras.rs new file mode 100644 index 000000000..50a7e3f93 --- /dev/null +++ b/crates/uv-requirements/src/extras.rs @@ -0,0 +1,137 @@ +use std::sync::Arc; + +use futures::{stream::FuturesOrdered, TryStreamExt}; + +use uv_distribution::{DistributionDatabase, Reporter}; +use uv_distribution_types::{Dist, DistributionMetadata}; +use uv_pypi_types::Requirement; +use uv_resolver::{InMemoryIndex, MetadataResponse}; +use uv_types::{BuildContext, HashStrategy}; + +use crate::{required_dist, Error}; + +/// A resolver to expand the requested extras for a set of requirements to include all defined +/// extras. +pub struct ExtrasResolver<'a, Context: BuildContext> { + /// 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. + database: DistributionDatabase<'a, Context>, +} + +impl<'a, Context: BuildContext> ExtrasResolver<'a, Context> { + /// Instantiate a new [`ExtrasResolver`] for a given set of requirements. + pub fn new( + hasher: &'a HashStrategy, + index: &'a InMemoryIndex, + database: DistributionDatabase<'a, Context>, + ) -> Self { + Self { + hasher, + index, + database, + } + } + + /// Set the [`Reporter`] to use for this resolver. + #[must_use] + pub fn with_reporter(self, reporter: impl Reporter + 'static) -> Self { + Self { + database: self.database.with_reporter(reporter), + ..self + } + } + + /// Expand the set of available extras for a given set of requirements. + pub async fn resolve( + self, + requirements: impl Iterator, + ) -> Result, Error> { + let Self { + hasher, + index, + database, + } = self; + requirements + .map(|requirement| async { + Self::resolve_requirement(requirement, hasher, index, &database) + .await + .map(Requirement::from) + }) + .collect::>() + .try_collect() + .await + } + + /// Expand the set of available extras for a given [`Requirement`]. + async fn resolve_requirement( + requirement: Requirement, + hasher: &HashStrategy, + index: &InMemoryIndex, + database: &DistributionDatabase<'a, Context>, + ) -> Result { + // Determine whether the requirement represents a local distribution and convert to a + // buildable distribution. + let Some(dist) = required_dist(&requirement)? else { + return Ok(requirement); + }; + + // Fetch the metadata for the distribution. + let metadata = { + let id = dist.version_id(); + if let Some(archive) = index + .distributions() + .get(&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. + archive.metadata.clone() + } else { + // Run the PEP 517 build process to extract metadata from the source distribution. + let archive = database + .get_or_build_wheel_metadata(&dist, hasher.get(&dist)) + .await + .map_err(|err| match &dist { + Dist::Built(built) => Error::Download(built.clone(), err), + Dist::Source(source) => { + if source.is_local() { + Error::Build(source.clone(), err) + } else { + Error::DownloadAndBuild(source.clone(), err) + } + } + })?; + + let metadata = archive.metadata.clone(); + + // Insert the metadata into the index. + index + .distributions() + .done(id, Arc::new(MetadataResponse::Found(archive))); + + metadata + } + }; + + // Sort extras for consistency. + let extras = { + let mut extras = metadata.provides_extras; + extras.sort_unstable(); + extras + }; + + Ok(Requirement { + extras, + ..requirement + }) + } +} diff --git a/crates/uv-requirements/src/lib.rs b/crates/uv-requirements/src/lib.rs index d11d152f8..23d9884e7 100644 --- a/crates/uv-requirements/src/lib.rs +++ b/crates/uv-requirements/src/lib.rs @@ -1,12 +1,96 @@ +use uv_distribution_types::{BuiltDist, Dist, GitSourceDist, SourceDist}; +use uv_git::GitUrl; +use uv_pypi_types::{Requirement, RequirementSource}; + +pub use crate::extras::*; pub use crate::lookahead::*; pub use crate::source_tree::*; pub use crate::sources::*; pub use crate::specification::*; pub use crate::unnamed::*; +mod extras; mod lookahead; mod source_tree; mod sources; mod specification; mod unnamed; pub mod upgrade; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Failed to download: `{0}`")] + Download(BuiltDist, #[source] uv_distribution::Error), + + #[error("Failed to download and build: `{0}`")] + DownloadAndBuild(SourceDist, #[source] uv_distribution::Error), + + #[error("Failed to build: `{0}`")] + Build(SourceDist, #[source] uv_distribution::Error), + + #[error(transparent)] + Distribution(#[from] uv_distribution::Error), + + #[error(transparent)] + DistributionTypes(#[from] uv_distribution_types::Error), + + #[error(transparent)] + WheelFilename(#[from] uv_distribution_filename::WheelFilenameError), +} + +/// Convert a [`Requirement`] into a [`Dist`], if it is a direct URL. +pub(crate) fn required_dist( + requirement: &Requirement, +) -> Result, uv_distribution_types::Error> { + Ok(Some(match &requirement.source { + RequirementSource::Registry { .. } => return Ok(None), + RequirementSource::Url { + subdirectory, + location, + ext, + url, + } => Dist::from_http_url( + requirement.name.clone(), + url.clone(), + location.clone(), + subdirectory.clone(), + *ext, + )?, + RequirementSource::Git { + repository, + reference, + precise, + subdirectory, + url, + } => { + let git_url = if let Some(precise) = precise { + GitUrl::from_commit(repository.clone(), reference.clone(), *precise) + } else { + GitUrl::from_reference(repository.clone(), reference.clone()) + }; + Dist::Source(SourceDist::Git(GitSourceDist { + name: requirement.name.clone(), + git: Box::new(git_url), + subdirectory: subdirectory.clone(), + url: url.clone(), + })) + } + RequirementSource::Path { + install_path, + ext, + url, + } => Dist::from_file_url(requirement.name.clone(), url.clone(), install_path, *ext)?, + RequirementSource::Directory { + install_path, + r#virtual, + url, + editable, + } => Dist::from_directory_url( + requirement.name.clone(), + url.clone(), + install_path, + *editable, + *r#virtual, + )?, + })) +} diff --git a/crates/uv-requirements/src/lookahead.rs b/crates/uv-requirements/src/lookahead.rs index 4e332586c..80c4da3aa 100644 --- a/crates/uv-requirements/src/lookahead.rs +++ b/crates/uv-requirements/src/lookahead.rs @@ -3,29 +3,17 @@ use std::{collections::VecDeque, sync::Arc}; use futures::stream::FuturesUnordered; use futures::StreamExt; use rustc_hash::FxHashSet; -use thiserror::Error; use tracing::trace; use uv_configuration::{Constraints, Overrides}; use uv_distribution::{DistributionDatabase, Reporter}; -use uv_distribution_types::{BuiltDist, Dist, DistributionMetadata, GitSourceDist, SourceDist}; -use uv_git::GitUrl; +use uv_distribution_types::{Dist, DistributionMetadata}; use uv_normalize::GroupName; use uv_pypi_types::{Requirement, RequirementSource}; use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverMarkers}; use uv_types::{BuildContext, HashStrategy, RequestedRequirements}; -#[derive(Debug, Error)] -pub enum LookaheadError { - #[error("Failed to download: `{0}`")] - Download(BuiltDist, #[source] uv_distribution::Error), - #[error("Failed to download and build: `{0}`")] - DownloadAndBuild(SourceDist, #[source] uv_distribution::Error), - #[error("Failed to build: `{0}`")] - Build(SourceDist, #[source] uv_distribution::Error), - #[error(transparent)] - UnsupportedUrl(#[from] uv_distribution_types::Error), -} +use crate::{required_dist, Error}; /// A resolver for resolving lookahead requirements from direct URLs. /// @@ -100,7 +88,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { pub async fn resolve( self, markers: &ResolverMarkers, - ) -> Result, LookaheadError> { + ) -> Result, Error> { let mut results = Vec::new(); let mut futures = FuturesUnordered::new(); let mut seen = FxHashSet::default(); @@ -146,7 +134,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { async fn lookahead( &self, requirement: Requirement, - ) -> Result, LookaheadError> { + ) -> Result, Error> { trace!("Performing lookahead for {requirement}"); // Determine whether the requirement represents a local distribution and convert to a @@ -180,12 +168,12 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { .get_or_build_wheel_metadata(&dist, self.hasher.get(&dist)) .await .map_err(|err| match &dist { - Dist::Built(built) => LookaheadError::Download(built.clone(), err), + Dist::Built(built) => Error::Download(built.clone(), err), Dist::Source(source) => { if source.is_local() { - LookaheadError::Build(source.clone(), err) + Error::Build(source.clone(), err) } else { - LookaheadError::DownloadAndBuild(source.clone(), err) + Error::DownloadAndBuild(source.clone(), err) } } })?; @@ -245,58 +233,3 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { ))) } } - -/// Convert a [`Requirement`] into a [`Dist`], if it is a direct URL. -fn required_dist(requirement: &Requirement) -> Result, uv_distribution_types::Error> { - Ok(Some(match &requirement.source { - RequirementSource::Registry { .. } => return Ok(None), - RequirementSource::Url { - subdirectory, - location, - ext, - url, - } => Dist::from_http_url( - requirement.name.clone(), - url.clone(), - location.clone(), - subdirectory.clone(), - *ext, - )?, - RequirementSource::Git { - repository, - reference, - precise, - subdirectory, - url, - } => { - let git_url = if let Some(precise) = precise { - GitUrl::from_commit(repository.clone(), reference.clone(), *precise) - } else { - GitUrl::from_reference(repository.clone(), reference.clone()) - }; - Dist::Source(SourceDist::Git(GitSourceDist { - name: requirement.name.clone(), - git: Box::new(git_url), - subdirectory: subdirectory.clone(), - url: url.clone(), - })) - } - RequirementSource::Path { - install_path, - ext, - url, - } => Dist::from_file_url(requirement.name.clone(), url.clone(), install_path, *ext)?, - RequirementSource::Directory { - install_path, - r#virtual, - url, - editable, - } => Dist::from_directory_url( - requirement.name.clone(), - url.clone(), - install_path, - *editable, - *r#virtual, - )?, - })) -} diff --git a/crates/uv-requirements/src/source_tree.rs b/crates/uv-requirements/src/source_tree.rs index 245710f47..54777611d 100644 --- a/crates/uv-requirements/src/source_tree.rs +++ b/crates/uv-requirements/src/source_tree.rs @@ -1,5 +1,5 @@ use std::borrow::Cow; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; use anyhow::{Context, Result}; @@ -34,8 +34,6 @@ pub struct SourceTreeResolution { /// 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> { - /// The requirements for the project. - source_trees: Vec, /// The extras to include when resolving requirements. extras: &'a ExtrasSpecification, /// The hash policy to enforce. @@ -49,14 +47,12 @@ pub struct SourceTreeResolver<'a, Context: BuildContext> { impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { /// Instantiate a new [`SourceTreeResolver`] for a given set of `source_trees`. pub fn new( - source_trees: Vec, extras: &'a ExtrasSpecification, hasher: &'a HashStrategy, index: &'a InMemoryIndex, database: DistributionDatabase<'a, Context>, ) -> Self { Self { - source_trees, extras, hasher, index, @@ -74,10 +70,11 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { } /// Resolve the requirements from the provided source trees. - pub async fn resolve(self) -> Result> { - let resolutions: Vec<_> = self - .source_trees - .iter() + pub async fn resolve( + self, + source_trees: impl Iterator, + ) -> Result> { + let resolutions: Vec<_> = source_trees .map(|source_tree| async { self.resolve_source_tree(source_tree).await }) .collect::>() .try_collect() diff --git a/crates/uv-requirements/src/sources.rs b/crates/uv-requirements/src/sources.rs index e59377c18..bf0651711 100644 --- a/crates/uv-requirements/src/sources.rs +++ b/crates/uv-requirements/src/sources.rs @@ -80,7 +80,8 @@ impl RequirementsSource { Self::RequirementsTxt(path) } - /// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a package. + /// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a positional + /// package (e.g., `uv pip install flask`). /// /// If the user provided a value that appears to be a `requirements.txt` file or a local /// directory, prompt them to correct it (if the terminal is interactive). @@ -121,6 +122,48 @@ impl RequirementsSource { Self::Package(name) } + /// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a `--with` + /// package (e.g., `uvx --with flask ruff`). + /// + /// If the user provided a value that appears to be a `requirements.txt` file or a local + /// directory, prompt them to correct it (if the terminal is interactive). + pub fn from_with_package(name: String) -> Self { + // If the user provided a `requirements.txt` file without `--with-requirements` (as in + // `uvx --with requirements.txt ruff`), prompt them to correct it. + #[allow(clippy::case_sensitive_file_extension_comparisons)] + if (name.ends_with(".txt") || name.ends_with(".in")) && Path::new(&name).is_file() { + let term = Term::stderr(); + if term.is_term() { + let prompt = format!( + "`{name}` looks like a local requirements file but was passed as a package name. Did you mean `--with-requirements {name}`?" + ); + let confirmation = uv_console::confirm(&prompt, &term, true).unwrap(); + if confirmation { + return Self::from_requirements_file(name.into()); + } + } + } + + // Similarly, if the user provided a `pyproject.toml` file without `--with-requirements` (as in + // `uvx --with pyproject.toml ruff`), prompt them to correct it. + if (name == "pyproject.toml" || name == "setup.py" || name == "setup.cfg") + && Path::new(&name).is_file() + { + let term = Term::stderr(); + if term.is_term() { + let prompt = format!( + "`{name}` looks like a local metadata file but was passed as a package name. Did you mean `--with-requirements {name}`?" + ); + let confirmation = uv_console::confirm(&prompt, &term, true).unwrap(); + if confirmation { + return Self::from_requirements_file(name.into()); + } + } + } + + Self::Package(name) + } + /// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a path to a source /// tree. pub fn from_source_tree(path: PathBuf) -> Self { diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index e456acaaa..bb491cb19 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -32,12 +32,11 @@ use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; use rustc_hash::FxHashSet; use tracing::instrument; - use uv_cache_key::CanonicalUrl; use uv_client::BaseClientBuilder; use uv_configuration::{NoBinary, NoBuild}; use uv_distribution_types::{ - FlatIndexLocation, IndexUrl, NameRequirementSpecification, UnresolvedRequirement, + IndexUrl, NameRequirementSpecification, UnresolvedRequirement, UnresolvedRequirementSpecification, }; use uv_fs::{Simplified, CWD}; @@ -71,7 +70,7 @@ pub struct RequirementsSpecification { /// Whether to disallow index usage. pub no_index: bool, /// The `--find-links` locations to use for fetching packages. - pub find_links: Vec, + pub find_links: Vec, /// The `--no-binary` flags to enforce when selecting distributions. pub no_binary: NoBinary, /// The `--no-build` flags to enforce when selecting distributions. @@ -142,7 +141,7 @@ impl RequirementsSpecification { find_links: requirements_txt .find_links .into_iter() - .map(FlatIndexLocation::from) + .map(IndexUrl::from) .collect(), no_binary: requirements_txt.no_binary, no_build: requirements_txt.only_binary, diff --git a/crates/uv-requirements/src/unnamed.rs b/crates/uv-requirements/src/unnamed.rs index cece00a6f..252cc368e 100644 --- a/crates/uv-requirements/src/unnamed.rs +++ b/crates/uv-requirements/src/unnamed.rs @@ -9,6 +9,7 @@ use serde::Deserialize; use tracing::debug; use url::Host; +use crate::Error; use uv_distribution::{DistributionDatabase, Reporter}; use uv_distribution_filename::{DistExtension, SourceDistFilename, WheelFilename}; use uv_distribution_types::{ @@ -22,22 +23,8 @@ use uv_pypi_types::{ParsedUrl, VerbatimParsedUrl}; use uv_resolver::{InMemoryIndex, MetadataResponse}; use uv_types::{BuildContext, HashStrategy}; -#[derive(Debug, thiserror::Error)] -pub enum NamedRequirementsError { - #[error(transparent)] - Distribution(#[from] uv_distribution::Error), - - #[error(transparent)] - DistributionTypes(#[from] uv_distribution_types::Error), - - #[error(transparent)] - WheelFilename(#[from] uv_distribution_filename::WheelFilenameError), -} - /// Like [`RequirementsSpecification`], but with concrete names for all requirements. pub struct NamedRequirementsResolver<'a, Context: BuildContext> { - /// The requirements for the project. - requirements: Vec>, /// Whether to check hashes for distributions. hasher: &'a HashStrategy, /// The in-memory index for resolving dependencies. @@ -47,15 +34,13 @@ pub struct NamedRequirementsResolver<'a, Context: BuildContext> { } impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { - /// Instantiate a new [`NamedRequirementsResolver`] for a given set of requirements. + /// Instantiate a new [`NamedRequirementsResolver`]. pub fn new( - requirements: Vec>, hasher: &'a HashStrategy, index: &'a InMemoryIndex, database: DistributionDatabase<'a, Context>, ) -> Self { Self { - requirements, hasher, index, database, @@ -72,15 +57,16 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { } /// Resolve any unnamed requirements in the specification. - pub async fn resolve(self) -> Result, NamedRequirementsError> { + pub async fn resolve( + self, + requirements: impl Iterator>, + ) -> Result, Error> { let Self { - requirements, hasher, index, database, } = self; requirements - .into_iter() .map(|requirement| async { Self::resolve_requirement(requirement, hasher, index, &database) .await @@ -97,7 +83,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { hasher: &HashStrategy, index: &InMemoryIndex, database: &DistributionDatabase<'a, Context>, - ) -> Result, NamedRequirementsError> { + ) -> Result, Error> { // If the requirement is a wheel, extract the package name from the wheel filename. // // Ex) `anyio-4.3.0-py3-none-any.whl` diff --git a/crates/uv-resolver/Cargo.toml b/crates/uv-resolver/Cargo.toml index a50aaae47..2c04a0327 100644 --- a/crates/uv-resolver/Cargo.toml +++ b/crates/uv-resolver/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -31,6 +34,7 @@ uv-pubgrub = { workspace = true } uv-pypi-types = { workspace = true } uv-python = { workspace = true } uv-requirements-txt = { workspace = true } +uv-static = { workspace = true } uv-types = { workspace = true } uv-warnings = { workspace = true } uv-workspace = { workspace = true } diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index efb5d6766..73f8bc634 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -6,12 +6,6 @@ use indexmap::IndexSet; use pubgrub::{DefaultStringReporter, DerivationTree, Derived, External, Range, Reporter}; use rustc_hash::FxHashMap; -use tracing::trace; -use uv_distribution_types::{BuiltDist, IndexLocations, IndexUrl, InstalledDist, SourceDist}; -use uv_normalize::PackageName; -use uv_pep440::Version; -use uv_pep508::MarkerTree; - use crate::candidate_selector::CandidateSelector; use crate::dependency_provider::UvDependencyProvider; use crate::fork_urls::ForkUrls; @@ -21,6 +15,15 @@ use crate::pubgrub::{ use crate::python_requirement::PythonRequirement; use crate::resolution::ConflictingDistributionError; use crate::resolver::{IncompletePackage, ResolverMarkers, UnavailablePackage, UnavailableReason}; +use crate::Options; +use tracing::trace; +use uv_distribution_types::{ + BuiltDist, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, SourceDist, +}; +use uv_normalize::PackageName; +use uv_pep440::Version; +use uv_pep508::MarkerTree; +use uv_static::EnvVars; #[derive(Debug, thiserror::Error)] pub enum ResolveError { @@ -52,6 +55,19 @@ pub enum ResolveError { fork_markers: MarkerTree, }, + #[error("Requirements contain conflicting indexes for package `{0}`:\n- {}", _1.join("\n- "))] + ConflictingIndexesUniversal(PackageName, Vec), + + #[error("Requirements contain conflicting indexes for package `{package_name}` in split `{fork_markers:?}`:\n- {}", indexes.join("\n- "))] + ConflictingIndexesFork { + package_name: PackageName, + indexes: Vec, + fork_markers: MarkerTree, + }, + + #[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")] + ConflictingIndexes(PackageName, String, String), + #[error("Package `{0}` attempted to resolve via URL: {1}. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{0} @ {1}` to your dependencies or constraints file.")] DisallowedUrl(PackageName, String), @@ -112,11 +128,13 @@ pub struct NoSolutionError { selector: CandidateSelector, python_requirement: PythonRequirement, index_locations: IndexLocations, + index_capabilities: IndexCapabilities, unavailable_packages: FxHashMap, incomplete_packages: FxHashMap>, fork_urls: ForkUrls, markers: ResolverMarkers, workspace_members: BTreeSet, + options: Options, } impl NoSolutionError { @@ -128,11 +146,13 @@ impl NoSolutionError { selector: CandidateSelector, python_requirement: PythonRequirement, index_locations: IndexLocations, + index_capabilities: IndexCapabilities, unavailable_packages: FxHashMap, incomplete_packages: FxHashMap>, fork_urls: ForkUrls, markers: ResolverMarkers, workspace_members: BTreeSet, + options: Options, ) -> Self { Self { error, @@ -141,11 +161,13 @@ impl NoSolutionError { selector, python_requirement, index_locations, + index_capabilities, unavailable_packages, incomplete_packages, fork_urls, markers, workspace_members, + options, } } @@ -213,7 +235,8 @@ impl std::fmt::Display for NoSolutionError { // Transform the error tree for reporting let mut tree = self.error.clone(); simplify_derivation_tree_markers(&self.python_requirement, &mut tree); - let should_display_tree = std::env::var_os("UV_INTERNAL__SHOW_DERIVATION_TREE").is_some() + let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE) + .is_some() || tracing::enabled!(tracing::Level::TRACE); if should_display_tree { @@ -243,12 +266,14 @@ impl std::fmt::Display for NoSolutionError { &tree, &self.selector, &self.index_locations, + &self.index_capabilities, &self.available_indexes, &self.unavailable_packages, &self.incomplete_packages, &self.fork_urls, &self.markers, &self.workspace_members, + self.options, &mut additional_hints, ); for hint in additional_hints { @@ -268,7 +293,7 @@ fn display_tree( display_tree_inner(error, &mut lines, 0); lines.reverse(); - if std::env::var_os("UV_INTERNAL__SHOW_DERIVATION_TREE").is_some() { + if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() { eprintln!("{name}\n{}", lines.join("\n")); } else { trace!("{name}\n{}", lines.join("\n")); diff --git a/crates/uv-resolver/src/fork_indexes.rs b/crates/uv-resolver/src/fork_indexes.rs new file mode 100644 index 000000000..48af8fbe3 --- /dev/null +++ b/crates/uv-resolver/src/fork_indexes.rs @@ -0,0 +1,48 @@ +use rustc_hash::FxHashMap; +use uv_distribution_types::IndexUrl; +use uv_normalize::PackageName; + +use crate::resolver::ResolverMarkers; +use crate::ResolveError; + +/// See [`crate::resolver::ForkState`]. +#[derive(Default, Debug, Clone)] +pub(crate) struct ForkIndexes(FxHashMap); + +impl ForkIndexes { + /// Get the [`IndexUrl`] previously used for a package in this fork. + pub(crate) fn get(&self, package_name: &PackageName) -> Option<&IndexUrl> { + self.0.get(package_name) + } + + /// Check that this is the only [`IndexUrl`] used for this package in this fork. + pub(crate) fn insert( + &mut self, + package_name: &PackageName, + index: &IndexUrl, + fork_markers: &ResolverMarkers, + ) -> Result<(), ResolveError> { + if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) { + if &previous != index { + let mut conflicts = vec![previous.to_string(), index.to_string()]; + conflicts.sort(); + return match fork_markers { + ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => { + Err(ResolveError::ConflictingIndexesUniversal( + package_name.clone(), + conflicts, + )) + } + ResolverMarkers::Fork(fork_markers) => { + Err(ResolveError::ConflictingIndexesFork { + package_name: package_name.clone(), + indexes: conflicts, + fork_markers: fork_markers.clone(), + }) + } + }; + } + } + Ok(()) + } +} diff --git a/crates/uv-resolver/src/fork_urls.rs b/crates/uv-resolver/src/fork_urls.rs index 39edf0e9b..5b11f2117 100644 --- a/crates/uv-resolver/src/fork_urls.rs +++ b/crates/uv-resolver/src/fork_urls.rs @@ -9,7 +9,7 @@ use uv_pypi_types::VerbatimParsedUrl; use crate::resolver::ResolverMarkers; use crate::ResolveError; -/// See [`crate::resolver::SolveState`]. +/// See [`crate::resolver::ForkState`]. #[derive(Default, Debug, Clone)] pub(crate) struct ForkUrls(FxHashMap); diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 9c83275b7..4d60b9f95 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -7,7 +7,7 @@ pub use lock::{ Lock, LockError, RequirementsTxtExport, ResolverManifest, SatisfiesResult, TreeDisplay, }; pub use manifest::Manifest; -pub use options::{Options, OptionsBuilder}; +pub use options::{Flexibility, Options, OptionsBuilder}; pub use preferences::{Preference, PreferenceError, Preferences}; pub use prerelease::PrereleaseMode; pub use pubgrub::{PubGrubSpecifier, PubGrubSpecifierError}; @@ -34,6 +34,7 @@ mod error; mod exclude_newer; mod exclusions; mod flat_index; +mod fork_indexes; mod fork_urls; mod graph_ops; mod lock; diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index ce1be4cd0..b2ebe4dbe 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -10,7 +10,7 @@ use std::fmt::{Debug, Display}; use std::io; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::LazyLock; +use std::sync::{Arc, LazyLock}; use toml_edit::{value, Array, ArrayOfTables, InlineTable, Item, Table, Value}; use url::Url; @@ -20,10 +20,9 @@ use uv_distribution::DistributionDatabase; use uv_distribution_filename::{DistExtension, ExtensionError, SourceDistExtension, WheelFilename}; use uv_distribution_types::{ BuiltDist, DependencyMetadata, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, - Dist, DistributionMetadata, FileLocation, FlatIndexLocation, GitSourceDist, HashPolicy, - IndexLocations, IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist, - RegistryBuiltWheel, RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, StaticMetadata, - ToUrlError, UrlString, + Dist, DistributionMetadata, FileLocation, GitSourceDist, IndexLocations, IndexUrl, Name, + PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist, + RemoteSource, Resolution, ResolvedDist, StaticMetadata, ToUrlError, UrlString, }; use uv_fs::{relative_to, PortablePath, PortablePathBuf}; use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference}; @@ -35,14 +34,17 @@ use uv_pypi_types::{ redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement, RequirementSource, ResolverMarkerEnvironment, }; -use uv_types::BuildContext; +use uv_types::{BuildContext, HashStrategy}; use uv_workspace::{InstallTarget, Workspace}; pub use crate::lock::requirements_txt::RequirementsTxtExport; pub use crate::lock::tree::TreeDisplay; use crate::requires_python::SimplifiedMarkerTree; use crate::resolution::{AnnotatedDist, ResolutionGraphNode}; -use crate::{ExcludeNewer, PrereleaseMode, RequiresPython, ResolutionGraph, ResolutionMode}; +use crate::{ + ExcludeNewer, InMemoryIndex, MetadataResponse, PrereleaseMode, RequiresPython, ResolutionGraph, + ResolutionMode, +}; mod requirements_txt; mod tree; @@ -927,6 +929,8 @@ impl Lock { indexes: Option<&IndexLocations>, build_options: &BuildOptions, tags: &Tags, + hasher: &HashStrategy, + index: &InMemoryIndex, database: &DistributionDatabase<'_, Context>, ) -> Result, LockError> { let mut queue: VecDeque<&Package> = VecDeque::new(); @@ -1053,53 +1057,31 @@ impl Lock { // Collect the set of available indexes (both `--index-url` and `--find-links` entries). let remotes = indexes.map(|locations| { locations - .indexes() - .filter_map(|index_url| match index_url { + .allowed_indexes() + .into_iter() + .filter_map(|index| match index.url() { IndexUrl::Pypi(_) | IndexUrl::Url(_) => { - Some(UrlString::from(index_url.redacted())) + Some(UrlString::from(index.url().redacted())) } IndexUrl::Path(_) => None, }) - .chain( - locations - .flat_index() - .filter_map(|index_url| match index_url { - FlatIndexLocation::Url(_) => { - Some(UrlString::from(index_url.redacted())) - } - FlatIndexLocation::Path(_) => None, - }), - ) .collect::>() }); let locals = indexes.map(|locations| { locations - .indexes() - .filter_map(|index_url| match index_url { + .allowed_indexes() + .into_iter() + .filter_map(|index| match index.url() { IndexUrl::Pypi(_) | IndexUrl::Url(_) => None, - IndexUrl::Path(index_url) => { - let path = index_url.to_file_path().ok()?; + IndexUrl::Path(url) => { + let path = url.to_file_path().ok()?; let path = relative_to(&path, workspace.install_path()) .or_else(|_| std::path::absolute(path)) .ok()?; Some(path) } }) - .chain( - locations - .flat_index() - .filter_map(|index_url| match index_url { - FlatIndexLocation::Url(_) => None, - FlatIndexLocation::Path(index_url) => { - let path = index_url.to_file_path().ok()?; - let path = relative_to(&path, workspace.install_path()) - .or_else(|_| std::path::absolute(path)) - .ok()?; - Some(path) - } - }), - ) .collect::>() }); @@ -1158,20 +1140,48 @@ impl Lock { build_options, )?; - let Ok(archive) = database - .get_or_build_wheel_metadata(&dist, HashPolicy::None) - .await - else { - return Ok(SatisfiesResult::MissingMetadata( - &package.id.name, - &package.id.version, - )); + // Fetch the metadata for the distribution. + let metadata = { + let id = dist.version_id(); + if let Some(archive) = + index + .distributions() + .get(&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. + archive.metadata.clone() + } else { + // Run the PEP 517 build process to extract metadata from the source distribution. + let archive = database + .get_or_build_wheel_metadata(&dist, hasher.get(&dist)) + .await + .map_err(|err| LockErrorKind::Resolution { + id: package.id.clone(), + err, + })?; + + let metadata = archive.metadata.clone(); + + // Insert the metadata into the index. + index + .distributions() + .done(id, Arc::new(MetadataResponse::Found(archive))); + + metadata + } }; // Validate the `requires-dist` metadata. { - let expected: BTreeSet<_> = archive - .metadata + let expected: BTreeSet<_> = metadata .requires_dist .into_iter() .map(|requirement| normalize_requirement(requirement, workspace)) @@ -1196,8 +1206,7 @@ impl Lock { // Validate the `dev-dependencies` metadata. { - let expected: BTreeMap> = archive - .metadata + let expected: BTreeMap> = metadata .dev_dependencies .into_iter() .map(|(group, requirements)| { @@ -1312,8 +1321,6 @@ pub enum SatisfiesResult<'lock> { MissingRemoteIndex(&'lock PackageName, &'lock Version, &'lock UrlString), /// The lockfile referenced a local index that was not provided MissingLocalIndex(&'lock PackageName, &'lock Version, &'lock PathBuf), - /// The resolver failed to generate metadata for a given package. - MissingMetadata(&'lock PackageName, &'lock Version), /// A package in the lockfile contains different `requires-dist` metadata than expected. MismatchedRequiresDist( &'lock PackageName, @@ -3758,6 +3765,13 @@ fn normalize_requirement( #[error(transparent)] pub struct LockError(Box); +impl LockError { + /// Returns true if the [`LockError`] is a resolver error. + pub fn is_resolution(&self) -> bool { + matches!(&*self.0, LockErrorKind::Resolution { .. }) + } +} + impl From for LockError where LockErrorKind: From, @@ -3777,14 +3791,14 @@ where enum LockErrorKind { /// An error that occurs when multiple packages with the same /// ID were found. - #[error("found duplicate package `{id}`")] + #[error("Found duplicate package `{id}`")] DuplicatePackage { /// The ID of the conflicting package. id: PackageId, }, /// An error that occurs when there are multiple dependencies for the /// same package that have identical identifiers. - #[error("for package `{id}`, found duplicate dependency `{dependency}`")] + #[error("For package `{id}`, found duplicate dependency `{dependency}`")] DuplicateDependency { /// The ID of the package for which a duplicate dependency was /// found. @@ -3795,7 +3809,7 @@ enum LockErrorKind { /// An error that occurs when there are multiple dependencies for the /// same package that have identical identifiers, as part of the /// that package's optional dependencies. - #[error("for package `{id}[{extra}]`, found duplicate dependency `{dependency}`")] + #[error("For package `{id}[{extra}]`, found duplicate dependency `{dependency}`")] DuplicateOptionalDependency { /// The ID of the package for which a duplicate dependency was /// found. @@ -3808,7 +3822,7 @@ enum LockErrorKind { /// An error that occurs when there are multiple dependencies for the /// same package that have identical identifiers, as part of the /// that package's development dependencies. - #[error("for package `{id}:{group}`, found duplicate dependency `{dependency}`")] + #[error("For package `{id}:{group}`, found duplicate dependency `{dependency}`")] DuplicateDevDependency { /// The ID of the package for which a duplicate dependency was /// found. @@ -3820,7 +3834,7 @@ enum LockErrorKind { }, /// An error that occurs when the URL to a file for a wheel or /// source dist could not be converted to a structured `url::Url`. - #[error("failed to parse wheel or source distribution URL")] + #[error("Failed to parse wheel or source distribution URL")] InvalidFileUrl( /// The underlying error that occurred. This includes the /// errant URL in its error message. @@ -3829,10 +3843,10 @@ enum LockErrorKind { ), /// An error that occurs when the extension can't be determined /// for a given wheel or source distribution. - #[error("failed to parse file extension; expected one of: {0}")] + #[error("Failed to parse file extension; expected one of: {0}")] MissingExtension(#[from] ExtensionError), /// Failed to parse a git source URL. - #[error("failed to parse source git URL")] + #[error("Failed to parse source git URL")] InvalidGitSourceUrl( /// The underlying error that occurred. This includes the /// errant URL in the message. @@ -3842,7 +3856,7 @@ enum LockErrorKind { /// An error that occurs when there's an unrecognized dependency. /// /// That is, a dependency for a package that isn't in the lockfile. - #[error("for package `{id}`, found dependency `{dependency}` with no locked package")] + #[error("For package `{id}`, found dependency `{dependency}` with no locked package")] UnrecognizedDependency { /// The ID of the package that has an unrecognized dependency. id: PackageId, @@ -3852,7 +3866,7 @@ enum LockErrorKind { }, /// An error that occurs when a hash is expected (or not) for a particular /// artifact, but one was not found (or was). - #[error("since the package `{id}` comes from a {source} dependency, a hash was {expected} but one was not found for {artifact_type}", source = id.source.name(), expected = if *expected { "expected" } else { "not expected" })] + #[error("Since the package `{id}` comes from a {source} dependency, a hash was {expected} but one was not found for {artifact_type}", source = id.source.name(), expected = if *expected { "expected" } else { "not expected" })] Hash { /// The ID of the package that has a missing hash. id: PackageId, @@ -3864,7 +3878,7 @@ enum LockErrorKind { }, /// An error that occurs when a package is included with an extra name, /// but no corresponding base package (i.e., without the extra) exists. - #[error("found package `{id}` with extra `{extra}` but no base package")] + #[error("Found package `{id}` with extra `{extra}` but no base package")] MissingExtraBase { /// The ID of the package that has a missing base. id: PackageId, @@ -3885,7 +3899,7 @@ enum LockErrorKind { }, /// An error that occurs from an invalid lockfile where a wheel comes from a non-wheel source /// such as a directory. - #[error("wheels cannot come from {source_type} sources")] + #[error("Wheels cannot come from {source_type} sources")] InvalidWheelSource { /// The ID of the distribution that has a missing base. id: PackageId, @@ -3894,7 +3908,7 @@ enum LockErrorKind { }, /// An error that occurs when a distribution indicates that it is sourced from a remote /// registry, but is missing a URL. - #[error("found registry distribution {name}=={version} without a valid URL")] + #[error("Found registry distribution `{name}=={version}` without a valid URL")] MissingUrl { /// The name of the distribution that is missing a URL. name: PackageName, @@ -3903,7 +3917,7 @@ enum LockErrorKind { }, /// An error that occurs when a distribution indicates that it is sourced from a local registry, /// but is missing a path. - #[error("found registry distribution {name}=={version} without a valid path")] + #[error("Found registry distribution `{name}=={version}` without a valid path")] MissingPath { /// The name of the distribution that is missing a path. name: PackageName, @@ -3912,34 +3926,34 @@ enum LockErrorKind { }, /// An error that occurs when a distribution indicates that it is sourced from a registry, but /// is missing a filename. - #[error("found registry distribution {id} without a valid filename")] + #[error("Found registry distribution `{id}` without a valid filename")] MissingFilename { /// The ID of the distribution that is missing a filename. id: PackageId, }, /// An error that occurs when a distribution is included with neither wheels nor a source /// distribution. - #[error("distribution {id} can't be installed because it doesn't have a source distribution or wheel for the current platform")] + #[error("Distribution `{id}` can't be installed because it doesn't have a source distribution or wheel for the current platform")] NeitherSourceDistNorWheel { /// The ID of the distribution. id: PackageId, }, /// An error that occurs when a distribution is marked as both `--no-binary` and `--no-build`. - #[error("distribution {id} can't be installed because it is marked as both `--no-binary` and `--no-build`")] + #[error("Distribution `{id}` can't be installed because it is marked as both `--no-binary` and `--no-build`")] NoBinaryNoBuild { /// The ID of the distribution. id: PackageId, }, /// An error that occurs when a distribution is marked as `--no-binary`, but no source /// distribution is available. - #[error("distribution {id} can't be installed because it is marked as `--no-binary` but has no source distribution")] + #[error("Distribution `{id}` can't be installed because it is marked as `--no-binary` but has no source distribution")] NoBinary { /// The ID of the distribution. id: PackageId, }, /// An error that occurs when a distribution is marked as `--no-build`, but no binary /// distribution is available. - #[error("distribution {id} can't be installed because it is marked as `--no-build` but has no binary distribution")] + #[error("Distribution `{id}` can't be installed because it is marked as `--no-build` but has no binary distribution")] NoBuild { /// The ID of the distribution. id: PackageId, @@ -3947,20 +3961,20 @@ enum LockErrorKind { /// An error that occurs when a wheel-only distribution is incompatible with the current /// platform. #[error( - "distribution {id} can't be installed because the binary distribution is incompatible with the current platform" + "distribution `{id}` can't be installed because the binary distribution is incompatible with the current platform" )] IncompatibleWheelOnly { /// The ID of the distribution. id: PackageId, }, /// An error that occurs when a wheel-only source is marked as `--no-binary`. - #[error("distribution {id} can't be installed because it is marked as `--no-binary` but is itself a binary distribution")] + #[error("Distribution `{id}` can't be installed because it is marked as `--no-binary` but is itself a binary distribution")] NoBinaryWheelOnly { /// The ID of the distribution. id: PackageId, }, /// An error that occurs when converting between URLs and paths. - #[error("found dependency `{id}` with no locked distribution")] + #[error("Found dependency `{id}` with no locked distribution")] VerbatimUrl { /// The ID of the distribution that has a missing base. id: PackageId, @@ -3969,14 +3983,14 @@ enum LockErrorKind { err: VerbatimUrlError, }, /// An error that occurs when parsing an existing requirement. - #[error("could not compute relative path between workspace and distribution")] + #[error("Could not compute relative path between workspace and distribution")] DistributionRelativePath( /// The inner error we forward. #[source] std::io::Error, ), /// An error that occurs when converting an index URL to a relative path - #[error("could not compute relative path between workspace and index")] + #[error("Could not compute relative path between workspace and index")] IndexRelativePath( /// The inner error we forward. #[source] @@ -3985,7 +3999,7 @@ enum LockErrorKind { /// An error that occurs when an ambiguous `package.dependency` is /// missing a `version` field. #[error( - "dependency {name} has missing `version` \ + "Dependency `{name}` has missing `version` \ field but has more than one matching package" )] MissingDependencyVersion { @@ -3995,7 +4009,7 @@ enum LockErrorKind { /// An error that occurs when an ambiguous `package.dependency` is /// missing a `source` field. #[error( - "dependency {name} has missing `source` \ + "Dependency `{name}` has missing `source` \ field but has more than one matching package" )] MissingDependencySource { @@ -4003,52 +4017,61 @@ enum LockErrorKind { name: PackageName, }, /// An error that occurs when parsing an existing requirement. - #[error("could not compute relative path between workspace and requirement")] + #[error("Could not compute relative path between workspace and requirement")] RequirementRelativePath( /// The inner error we forward. #[source] std::io::Error, ), /// An error that occurs when parsing an existing requirement. - #[error("could not convert between URL and path")] + #[error("Could not convert between URL and path")] RequirementVerbatimUrl( /// The inner error we forward. #[source] VerbatimUrlError, ), /// An error that occurs when parsing a registry's index URL. - #[error("could not convert between URL and path")] + #[error("Could not convert between URL and path")] RegistryVerbatimUrl( /// The inner error we forward. #[source] VerbatimUrlError, ), /// An error that occurs when converting a path to a URL. - #[error("failed to convert path to URL")] + #[error("Failed to convert path to URL")] PathToUrl, /// An error that occurs when converting a URL to a path - #[error("failed to convert URL to path")] + #[error("Failed to convert URL to path")] UrlToPath, /// An error that occurs when multiple packages with the same /// name were found when identifying the root packages. - #[error("found multiple packages matching `{name}`")] + #[error("Found multiple packages matching `{name}`")] MultipleRootPackages { /// The ID of the package. name: PackageName, }, /// An error that occurs when a root package can't be found. - #[error("could not find root package `{name}`")] + #[error("Could not find root package `{name}`")] MissingRootPackage { /// The ID of the package. name: PackageName, }, + /// An error that occurs when resolving metadata for a package. + #[error("Failed to generate package metadata for `{id}`")] + Resolution { + /// The ID of the distribution that failed to resolve. + id: PackageId, + /// The inner error we forward. + #[source] + err: uv_distribution::Error, + }, } /// An error that occurs when a source string could not be parsed. -#[derive(Clone, Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error)] enum SourceParseError { /// An error that occurs when the URL in the source is invalid. - #[error("invalid URL in source `{given}`")] + #[error("Invalid URL in source `{given}`")] InvalidUrl { /// The source string given. given: String, @@ -4057,13 +4080,13 @@ enum SourceParseError { err: url::ParseError, }, /// An error that occurs when a Git URL is missing a precise commit SHA. - #[error("missing SHA in source `{given}`")] + #[error("Missing SHA in source `{given}`")] MissingSha { /// The source string given. given: String, }, /// An error that occurs when a Git URL has an invalid SHA. - #[error("invalid SHA in source `{given}`")] + #[error("Invalid SHA in source `{given}`")] InvalidSha { /// The source string given. given: String, @@ -4110,286 +4133,4 @@ fn each_element_on_its_line_array(elements: impl Iterator = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn missing_dependency_version_unambiguous() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "a" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package]] -name = "b" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package.dependencies]] -name = "a" -source = { registry = "https://pypi.org/simple" } -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn missing_dependency_source_version_unambiguous() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "a" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package]] -name = "b" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package.dependencies]] -name = "a" -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn missing_dependency_source_ambiguous() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "a" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package]] -name = "a" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package]] -name = "b" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package.dependencies]] -name = "a" -version = "0.1.0" -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn missing_dependency_version_ambiguous() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "a" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package]] -name = "a" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package]] -name = "b" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package.dependencies]] -name = "a" -source = { registry = "https://pypi.org/simple" } -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn missing_dependency_source_version_ambiguous() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "a" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package]] -name = "a" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package]] -name = "b" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } - -[[package.dependencies]] -name = "a" -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn hash_optional_missing() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "anyio" -version = "4.3.0" -source = { registry = "https://pypi.org/simple" } -wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl" }] -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn hash_optional_present() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "anyio" -version = "4.3.0" -source = { registry = "https://pypi.org/simple" } -wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" }] -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn hash_required_present() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "anyio" -version = "4.3.0" -source = { path = "file:///foo/bar" } -wheels = [{ url = "file:///foo/bar/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" }] -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn source_direct_no_subdir() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "anyio" -version = "4.3.0" -source = { url = "https://burntsushi.net" } -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn source_direct_has_subdir() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "anyio" -version = "4.3.0" -source = { url = "https://burntsushi.net", subdirectory = "wat/foo/bar" } -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn source_directory() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "anyio" -version = "4.3.0" -source = { directory = "path/to/dir" } -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } - - #[test] - fn source_editable() { - let data = r#" -version = 1 -requires-python = ">=3.12" - -[[package]] -name = "anyio" -version = "4.3.0" -source = { editable = "path/to/dir" } -"#; - let result: Result = toml::from_str(data); - insta::assert_debug_snapshot!(result); - } -} +mod tests; diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_ambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_ambiguous.snap index 43cd3e604..9de267f94 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_ambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_ambiguous.snap @@ -6,7 +6,7 @@ Err( Error { inner: Error { inner: TomlError { - message: "dependency a has missing `source` field but has more than one matching package", + message: "Dependency `a` has missing `source` field but has more than one matching package", raw: None, keys: [], span: None, diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_ambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_ambiguous.snap index d02a3886d..39161188c 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_ambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_ambiguous.snap @@ -6,7 +6,7 @@ Err( Error { inner: Error { inner: TomlError { - message: "dependency a has missing `version` field but has more than one matching package", + message: "Dependency `a` has missing `version` field but has more than one matching package", raw: None, keys: [], span: None, diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_ambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_ambiguous.snap index d02a3886d..39161188c 100644 --- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_ambiguous.snap +++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_ambiguous.snap @@ -6,7 +6,7 @@ Err( Error { inner: Error { inner: TomlError { - message: "dependency a has missing `version` field but has more than one matching package", + message: "Dependency `a` has missing `version` field but has more than one matching package", raw: None, keys: [], span: None, diff --git a/crates/uv-resolver/src/lock/tests.rs b/crates/uv-resolver/src/lock/tests.rs new file mode 100644 index 000000000..40e03a027 --- /dev/null +++ b/crates/uv-resolver/src/lock/tests.rs @@ -0,0 +1,281 @@ +use super::*; + +#[test] +fn missing_dependency_source_unambiguous() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "a" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "b" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package.dependencies]] +name = "a" +version = "0.1.0" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn missing_dependency_version_unambiguous() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "a" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "b" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package.dependencies]] +name = "a" +source = { registry = "https://pypi.org/simple" } +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn missing_dependency_source_version_unambiguous() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "a" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "b" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package.dependencies]] +name = "a" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn missing_dependency_source_ambiguous() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "a" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "a" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "b" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package.dependencies]] +name = "a" +version = "0.1.0" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn missing_dependency_version_ambiguous() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "a" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "a" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "b" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package.dependencies]] +name = "a" +source = { registry = "https://pypi.org/simple" } +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn missing_dependency_source_version_ambiguous() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "a" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "a" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package]] +name = "b" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[package.dependencies]] +name = "a" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn hash_optional_missing() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl" }] +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn hash_optional_present() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" }] +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn hash_required_present() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.3.0" +source = { path = "file:///foo/bar" } +wheels = [{ url = "file:///foo/bar/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8" }] +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn source_direct_no_subdir() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.3.0" +source = { url = "https://burntsushi.net" } +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn source_direct_has_subdir() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.3.0" +source = { url = "https://burntsushi.net", subdirectory = "wat/foo/bar" } +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn source_directory() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.3.0" +source = { directory = "path/to/dir" } +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn source_editable() { + let data = r#" +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "anyio" +version = "4.3.0" +source = { editable = "path/to/dir" } +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); +} diff --git a/crates/uv-resolver/src/lock/tree.rs b/crates/uv-resolver/src/lock/tree.rs index f5a3484e8..3ce6a2dbf 100644 --- a/crates/uv-resolver/src/lock/tree.rs +++ b/crates/uv-resolver/src/lock/tree.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; +use uv_configuration::DevMode; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pypi_types::ResolverMarkerEnvironment; @@ -22,8 +23,10 @@ pub struct TreeDisplay<'env> { optional_dependencies: FxHashMap<&'env PackageId, FxHashMap>>>, dev_dependencies: FxHashMap<&'env PackageId, FxHashMap>>>, - /// Maximum display depth of the dependency tree + /// Maximum display depth of the dependency tree. depth: usize, + /// Whether to include development dependencies in the display. + dev: DevMode, /// Prune the given packages from the display of the dependency tree. prune: Vec, /// Display only the specified packages. @@ -40,6 +43,7 @@ impl<'env> TreeDisplay<'env> { depth: usize, prune: Vec, packages: Vec, + dev: DevMode, no_dedupe: bool, invert: bool, ) -> Self { @@ -180,6 +184,7 @@ impl<'env> TreeDisplay<'env> { optional_dependencies, dev_dependencies, depth, + dev, prune, packages, no_dedupe, @@ -231,12 +236,14 @@ impl<'env> TreeDisplay<'env> { let dependencies: Vec> = self .dependencies .get(node.package_id()) + .filter(|_| self.dev != DevMode::Only) .into_iter() .flatten() .map(|dep| Node::Dependency(dep.as_ref())) .chain( self.optional_dependencies .get(node.package_id()) + .filter(|_| self.dev != DevMode::Only) .into_iter() .flatten() .flat_map(|(extra, deps)| { @@ -247,6 +254,7 @@ impl<'env> TreeDisplay<'env> { .chain( self.dev_dependencies .get(node.package_id()) + .filter(|_| self.dev != DevMode::Exclude) .into_iter() .flatten() .flat_map(|(group, deps)| { diff --git a/crates/uv-resolver/src/options.rs b/crates/uv-resolver/src/options.rs index 8351eaef9..7f2c1010e 100644 --- a/crates/uv-resolver/src/options.rs +++ b/crates/uv-resolver/src/options.rs @@ -3,13 +3,14 @@ use uv_configuration::IndexStrategy; use crate::{DependencyMode, ExcludeNewer, PrereleaseMode, ResolutionMode}; /// Options for resolving a manifest. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Options { pub resolution_mode: ResolutionMode, pub prerelease_mode: PrereleaseMode, pub dependency_mode: DependencyMode, pub exclude_newer: Option, pub index_strategy: IndexStrategy, + pub flexibility: Flexibility, } /// Builder for [`Options`]. @@ -20,6 +21,7 @@ pub struct OptionsBuilder { dependency_mode: DependencyMode, exclude_newer: Option, index_strategy: IndexStrategy, + flexibility: Flexibility, } impl OptionsBuilder { @@ -63,6 +65,13 @@ impl OptionsBuilder { self } + /// Sets the [`Flexibility`]. + #[must_use] + pub fn flexibility(mut self, flexibility: Flexibility) -> Self { + self.flexibility = flexibility; + self + } + /// Builds the options. pub fn build(self) -> Options { Options { @@ -71,6 +80,19 @@ impl OptionsBuilder { dependency_mode: self.dependency_mode, exclude_newer: self.exclude_newer, index_strategy: self.index_strategy, + flexibility: self.flexibility, } } } + +/// Whether the [`Options`] are configurable or fixed. +/// +/// Applies to the [`ResolutionMode`], [`PrereleaseMode`], and [`DependencyMode`] fields. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum Flexibility { + /// The setting is configurable. + #[default] + Configurable, + /// The setting is fixed. + Fixed, +} diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index decf99087..abd616e55 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -9,7 +9,7 @@ use pubgrub::{DerivationTree, Derived, External, Map, Range, ReportFormatter, Te use rustc_hash::FxHashMap; use uv_configuration::IndexStrategy; -use uv_distribution_types::{IndexLocations, IndexUrl}; +use uv_distribution_types::{Index, IndexCapabilities, IndexLocations, IndexUrl}; use uv_normalize::PackageName; use uv_pep440::Version; @@ -19,7 +19,7 @@ use crate::fork_urls::ForkUrls; use crate::prerelease::AllowPrerelease; use crate::python_requirement::{PythonRequirement, PythonRequirementSource}; use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason}; -use crate::{RequiresPython, ResolverMarkers}; +use crate::{Flexibility, Options, RequiresPython, ResolverMarkers}; use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; @@ -505,12 +505,14 @@ impl PubGrubReportFormatter<'_> { derivation_tree: &ErrorTree, selector: &CandidateSelector, index_locations: &IndexLocations, + index_capabilities: &IndexCapabilities, available_indexes: &FxHashMap>, unavailable_packages: &FxHashMap, incomplete_packages: &FxHashMap>, fork_urls: &ForkUrls, markers: &ResolverMarkers, workspace_members: &BTreeSet, + options: Options, output_hints: &mut IndexSet, ) { match derivation_tree { @@ -519,15 +521,17 @@ impl PubGrubReportFormatter<'_> { ) => { if let PubGrubPackageInner::Package { name, .. } = &**package { // Check for no versions due to pre-release options. - if !fork_urls.contains_key(name) { - self.prerelease_available_hint( - package, - name, - set, - selector, - markers, - output_hints, - ); + if options.flexibility == Flexibility::Configurable { + if !fork_urls.contains_key(name) { + self.prerelease_available_hint( + package, + name, + set, + selector, + markers, + output_hints, + ); + } } // Check for no versions due to no `--find-links` flat index. @@ -537,6 +541,7 @@ impl PubGrubReportFormatter<'_> { set, selector, index_locations, + index_capabilities, available_indexes, unavailable_packages, incomplete_packages, @@ -586,24 +591,28 @@ impl PubGrubReportFormatter<'_> { &derived.cause1, selector, index_locations, + index_capabilities, available_indexes, unavailable_packages, incomplete_packages, fork_urls, markers, workspace_members, + options, output_hints, ); self.generate_hints( &derived.cause2, selector, index_locations, + index_capabilities, available_indexes, unavailable_packages, incomplete_packages, fork_urls, markers, workspace_members, + options, output_hints, ); } @@ -616,12 +625,13 @@ impl PubGrubReportFormatter<'_> { set: &Range, selector: &CandidateSelector, index_locations: &IndexLocations, + index_capabilities: &IndexCapabilities, available_indexes: &FxHashMap>, unavailable_packages: &FxHashMap, incomplete_packages: &FxHashMap>, hints: &mut IndexSet, ) { - let no_find_links = index_locations.flat_index().peekable().peek().is_none(); + let no_find_links = index_locations.flat_indexes().peekable().peek().is_none(); // Add hints due to the package being entirely unavailable. match unavailable_packages.get(name) { @@ -703,6 +713,7 @@ impl PubGrubReportFormatter<'_> { // indexes were not queried, and could contain a compatible version. if let Some(next_index) = index_locations .indexes() + .map(Index::url) .skip_while(|url| *url != found_index) .nth(1) { @@ -715,6 +726,20 @@ impl PubGrubReportFormatter<'_> { } } } + + // Add hints due to an index returning an unauthorized response. + for index in index_locations.indexes() { + if index_capabilities.unauthorized(&index.url) { + hints.insert(PubGrubHint::UnauthorizedIndex { + index: index.url.clone(), + }); + } + if index_capabilities.forbidden(&index.url) { + hints.insert(PubGrubHint::ForbiddenIndex { + index: index.url.clone(), + }); + } + } } fn prerelease_available_hint( @@ -867,6 +892,10 @@ pub(crate) enum PubGrubHint { // excluded from `PartialEq` and `Hash` next_index: IndexUrl, }, + /// An index returned an Unauthorized (401) response. + UnauthorizedIndex { index: IndexUrl }, + /// An index returned a Forbidden (403) response. + ForbiddenIndex { index: IndexUrl }, } /// This private enum mirrors [`PubGrubHint`] but only includes fields that should be @@ -915,6 +944,12 @@ enum PubGrubHintCore { UncheckedIndex { package: PubGrubPackage, }, + UnauthorizedIndex { + index: IndexUrl, + }, + ForbiddenIndex { + index: IndexUrl, + }, } impl From for PubGrubHintCore { @@ -968,6 +1003,8 @@ impl From for PubGrubHintCore { workspace, }, PubGrubHint::UncheckedIndex { package, .. } => Self::UncheckedIndex { package }, + PubGrubHint::UnauthorizedIndex { index } => Self::UnauthorizedIndex { index }, + PubGrubHint::ForbiddenIndex { index } => Self::ForbiddenIndex { index }, } } } @@ -995,29 +1032,32 @@ impl std::fmt::Display for PubGrubHint { Self::PrereleaseAvailable { package, version } => { write!( f, - "{}{} Pre-releases are available for {} in the requested range (e.g., {}), but pre-releases weren't enabled (try: `--prerelease=allow`)", + "{}{} Pre-releases are available for {} in the requested range (e.g., {}), but pre-releases weren't enabled (try: `{}`)", "hint".bold().cyan(), ":".bold(), package.bold(), - version.bold() + version.bold(), + "--prerelease=allow".green(), ) } Self::PrereleaseRequested { package, range } => { write!( f, - "{}{} {} was requested with a pre-release marker (e.g., {}), but pre-releases weren't enabled (try: `--prerelease=allow`)", + "{}{} {} was requested with a pre-release marker (e.g., {}), but pre-releases weren't enabled (try: `{}`)", "hint".bold().cyan(), ":".bold(), package.bold(), - PackageRange::compatibility(package, range, None).bold() + PackageRange::compatibility(package, range, None).bold(), + "--prerelease=allow".green(), ) } Self::NoIndex => { write!( f, - "{}{} Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links `)", + "{}{} Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `{}`)", "hint".bold().cyan(), ":".bold(), + "--find-links ".green(), ) } Self::Offline => { @@ -1183,8 +1223,8 @@ impl std::fmt::Display for PubGrubHint { "{}{} The package `{}` depends on the package `{}` but the name is shadowed by {your_project}. Consider changing the name of {the_project}.", "hint".bold().cyan(), ":".bold(), - package, - dependency, + package.bold(), + dependency.bold(), ) } Self::UncheckedIndex { @@ -1198,13 +1238,33 @@ impl std::fmt::Display for PubGrubHint { "{}{} `{}` was found on {}, but not at the requested version ({}). A compatible version may be available on a subsequent index (e.g., {}). By default, uv will only consider versions that are published on the first index that contains a given package, to avoid dependency confusion attacks. If all indexes are equally trusted, use `{}` to consider all versions from all indexes, regardless of the order in which they were defined.", "hint".bold().cyan(), ":".bold(), - package, + package.bold(), found_index.cyan(), PackageRange::compatibility(package, range, None).cyan(), next_index.cyan(), "--index-strategy unsafe-best-match".green(), ) } + Self::UnauthorizedIndex { index } => { + write!( + f, + "{}{} An index URL ({}) could not be queried due to a lack of valid authentication credentials ({}).", + "hint".bold().cyan(), + ":".bold(), + index.redacted().cyan(), + "401 Unauthorized".bold().red(), + ) + } + Self::ForbiddenIndex { index } => { + write!( + f, + "{}{} An index URL ({}) could not be queried due to a lack of valid authentication credentials ({}).", + "hint".bold().cyan(), + ":".bold(), + index.redacted().cyan(), + "403 Forbidden".bold().red(), + ) + } } } } diff --git a/crates/uv-resolver/src/redirect.rs b/crates/uv-resolver/src/redirect.rs index a4a7652f4..60004c394 100644 --- a/crates/uv-resolver/src/redirect.rs +++ b/crates/uv-resolver/src/redirect.rs @@ -86,65 +86,4 @@ fn apply_redirect(url: &VerbatimUrl, redirect: Url) -> VerbatimUrl { } #[cfg(test)] -mod tests { - use url::Url; - - use uv_pep508::VerbatimUrl; - - use crate::redirect::apply_redirect; - - #[test] - fn test_apply_redirect() -> Result<(), url::ParseError> { - // If there's no `@` in the original representation, we can just append the precise suffix - // to the given representation. - let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git")? - .with_given("git+https://github.com/flask.git"); - let redirect = - Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?; - - let expected = VerbatimUrl::parse_url( - "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe", - )? - .with_given("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe"); - assert_eq!(apply_redirect(&verbatim, redirect), expected); - - // If there's an `@` in the original representation, and it's stable between the parsed and - // given representations, we preserve everything that precedes the `@` in the precise - // representation. - let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")? - .with_given("git+https://${DOMAIN}.com/flask.git@main"); - let redirect = - Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?; - - let expected = VerbatimUrl::parse_url( - "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe", - )? - .with_given("https://${DOMAIN}.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe"); - assert_eq!(apply_redirect(&verbatim, redirect), expected); - - // If there's a conflict after the `@`, discard the original representation. - let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")? - .with_given("git+https://github.com/flask.git@${TAG}".to_string()); - let redirect = - Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?; - - let expected = VerbatimUrl::parse_url( - "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe", - )?; - assert_eq!(apply_redirect(&verbatim, redirect), expected); - - // We should preserve subdirectory fragments. - let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git#subdirectory=src")? - .with_given("git+https://github.com/flask.git#subdirectory=src"); - let redirect = - Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src")?; - - let expected = VerbatimUrl::parse_url( - "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src", - )?.with_given("git+https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src"); - - assert_eq!(apply_redirect(&verbatim, redirect), expected); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-resolver/src/redirect/tests.rs b/crates/uv-resolver/src/redirect/tests.rs new file mode 100644 index 000000000..6a151adc8 --- /dev/null +++ b/crates/uv-resolver/src/redirect/tests.rs @@ -0,0 +1,61 @@ +use url::Url; + +use uv_pep508::VerbatimUrl; + +use crate::redirect::apply_redirect; + +#[test] +fn test_apply_redirect() -> Result<(), url::ParseError> { + // If there's no `@` in the original representation, we can just append the precise suffix + // to the given representation. + let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git")? + .with_given("git+https://github.com/flask.git"); + let redirect = + Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?; + + let expected = VerbatimUrl::parse_url( + "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe", + )? + .with_given("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe"); + assert_eq!(apply_redirect(&verbatim, redirect), expected); + + // If there's an `@` in the original representation, and it's stable between the parsed and + // given representations, we preserve everything that precedes the `@` in the precise + // representation. + let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")? + .with_given("git+https://${DOMAIN}.com/flask.git@main"); + let redirect = + Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?; + + let expected = VerbatimUrl::parse_url( + "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe", + )? + .with_given("https://${DOMAIN}.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe"); + assert_eq!(apply_redirect(&verbatim, redirect), expected); + + // If there's a conflict after the `@`, discard the original representation. + let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git@main")? + .with_given("git+https://github.com/flask.git@${TAG}".to_string()); + let redirect = + Url::parse("https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe")?; + + let expected = VerbatimUrl::parse_url( + "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe", + )?; + assert_eq!(apply_redirect(&verbatim, redirect), expected); + + // We should preserve subdirectory fragments. + let verbatim = VerbatimUrl::parse_url("https://github.com/flask.git#subdirectory=src")? + .with_given("git+https://github.com/flask.git#subdirectory=src"); + let redirect = Url::parse( + "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src", + )?; + + let expected = VerbatimUrl::parse_url( + "https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src", + )?.with_given("git+https://github.com/flask.git@b90a4f1f4a370e92054b9cc9db0efcb864f87ebe#subdirectory=src"); + + assert_eq!(apply_redirect(&verbatim, redirect), expected); + + Ok(()) +} diff --git a/crates/uv-resolver/src/requires_python.rs b/crates/uv-resolver/src/requires_python.rs index 3ecf7c446..f6d2869f1 100644 --- a/crates/uv-resolver/src/requires_python.rs +++ b/crates/uv-resolver/src/requires_python.rs @@ -1,13 +1,13 @@ +use itertools::Itertools; +use pubgrub::Range; use std::cmp::Ordering; use std::collections::Bound; use std::ops::Deref; -use itertools::Itertools; -use pubgrub::Range; - use uv_distribution_filename::WheelFilename; use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers}; use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion}; +use uv_pubgrub::PubGrubSpecifier; #[derive(thiserror::Error, Debug)] pub enum RequiresPythonError { @@ -53,11 +53,10 @@ impl RequiresPython { /// Returns a [`RequiresPython`] from a version specifier. pub fn from_specifiers(specifiers: &VersionSpecifiers) -> Result { - let (lower_bound, upper_bound) = - crate::pubgrub::PubGrubSpecifier::from_release_specifiers(specifiers)? - .bounding_range() - .map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned())) - .unwrap_or((Bound::Unbounded, Bound::Unbounded)); + let (lower_bound, upper_bound) = PubGrubSpecifier::from_release_specifiers(specifiers)? + .bounding_range() + .map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned())) + .unwrap_or((Bound::Unbounded, Bound::Unbounded)); Ok(Self { specifiers: specifiers.clone(), range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)), @@ -73,7 +72,7 @@ impl RequiresPython { // Convert to PubGrub range and perform an intersection. let range = specifiers .into_iter() - .map(crate::pubgrub::PubGrubSpecifier::from_release_specifiers) + .map(PubGrubSpecifier::from_release_specifiers) .fold_ok(None, |range: Option>, requires_python| { if let Some(range) = range { Some(range.intersection(&requires_python.into())) @@ -89,18 +88,20 @@ impl RequiresPython { // Extract the bounds. let (lower_bound, upper_bound) = range .bounding_range() - .map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned())) - .unwrap_or((Bound::Unbounded, Bound::Unbounded)); + .map(|(lower_bound, upper_bound)| { + ( + LowerBound(lower_bound.cloned()), + UpperBound(upper_bound.cloned()), + ) + }) + .unwrap_or((LowerBound::default(), UpperBound::default())); // Convert back to PEP 440 specifiers. - let specifiers = range - .iter() - .flat_map(VersionSpecifier::from_release_only_bounds) - .collect(); + let specifiers = VersionSpecifiers::from_release_only_bounds(range.iter()); Ok(Some(Self { specifiers, - range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)), + range: RequiresPythonRange(lower_bound, upper_bound), })) } @@ -222,7 +223,7 @@ impl RequiresPython { /// provided range. However, `>=3.9` would not be considered compatible, as the /// `Requires-Python` includes Python 3.8, but `>=3.9` does not. pub fn is_contained_by(&self, target: &VersionSpecifiers) -> bool { - let Ok(target) = crate::pubgrub::PubGrubSpecifier::from_release_specifiers(target) else { + let Ok(target) = PubGrubSpecifier::from_release_specifiers(target) else { return false; }; let target = target @@ -231,29 +232,10 @@ impl RequiresPython { .map(|(lower, _)| lower) .unwrap_or(&Bound::Unbounded); - // We want, e.g., `requires_python_lower` to be `>=3.8` and `version_lower` to be - // `>=3.7`. + // We want, e.g., `self.range.lower()` to be `>=3.8` and `target` to be `>=3.7`. // - // That is: `version_lower` should be less than or equal to `requires_python_lower`. - match (target, self.range.lower().as_ref()) { - (Bound::Included(target_lower), Bound::Included(requires_python_lower)) => { - target_lower <= requires_python_lower - } - (Bound::Excluded(target_lower), Bound::Included(requires_python_lower)) => { - target_lower < requires_python_lower - } - (Bound::Included(target_lower), Bound::Excluded(requires_python_lower)) => { - target_lower <= requires_python_lower - } - (Bound::Excluded(target_lower), Bound::Excluded(requires_python_lower)) => { - target_lower < requires_python_lower - } - // If the dependency has no lower bound, then it supports all versions. - (Bound::Unbounded, _) => true, - // If we have no lower bound, then there must be versions we support that the - // dependency does not. - (_, Bound::Unbounded) => false, - } + // That is: `target` should be less than or equal to `self.range.lower()`. + *self.range.lower() >= LowerBound(target.clone()) } /// Returns the [`VersionSpecifiers`] for the `Requires-Python` specifier. @@ -266,6 +248,18 @@ impl RequiresPython { self.range.lower().as_ref() == Bound::Unbounded } + /// Returns `true` if the `Requires-Python` specifier is set to an exact version + /// without specifying a patch version. (e.g. `==3.10`) + pub fn is_exact_without_patch(&self) -> bool { + match self.range.lower().as_ref() { + Bound::Included(version) => { + version.release().len() == 2 + && self.range.upper().as_ref() == Bound::Included(version) + } + _ => false, + } + } + /// Returns the [`RequiresPythonBound`] truncated to the major and minor version. pub fn bound_major_minor(&self) -> LowerBound { match self.range.lower().as_ref() { @@ -368,8 +362,8 @@ impl RequiresPython { return true; }; - // Ex) If the wheel bound is `3.12`, then it doesn't match `==3.10.*`. - let wheel_bound = UpperBound(Bound::Excluded(Version::new([3, minor + 1]))); + // Ex) If the wheel bound is `3.12`, then it doesn't match `<=3.10.`. + let wheel_bound = UpperBound(Bound::Included(Version::new([3, minor]))); if wheel_bound > self.range.upper().major_minor() { return false; } @@ -393,7 +387,8 @@ impl RequiresPython { return false; } - let wheel_bound = UpperBound(Bound::Excluded(Version::new([3, minor + 1]))); + // Ex) If the wheel bound is `3.12`, then it doesn't match `<=3.10.`. + let wheel_bound = UpperBound(Bound::Included(Version::new([3, minor]))); if wheel_bound > self.range.upper().major_minor() { return false; } @@ -421,8 +416,8 @@ impl RequiresPython { return false; } - // Ex) If the wheel bound is `3.12`, then it doesn't match `==3.10.*`. - let wheel_bound = UpperBound(Bound::Excluded(Version::new([3, minor + 1]))); + // Ex) If the wheel bound is `3.12`, then it doesn't match `<=3.10.`. + let wheel_bound = UpperBound(Bound::Included(Version::new([3, minor]))); if wheel_bound > self.range.upper().major_minor() { return false; } @@ -446,8 +441,8 @@ impl RequiresPython { return false; } - // Ex) If the wheel bound is `3.12`, then it doesn't match `==3.10.*`. - let wheel_bound = UpperBound(Bound::Excluded(Version::new([3, minor + 1]))); + // Ex) If the wheel bound is `3.12`, then it doesn't match `<=3.10.`. + let wheel_bound = UpperBound(Bound::Included(Version::new([3, minor]))); if wheel_bound > self.range.upper().major_minor() { return false; } @@ -476,12 +471,11 @@ impl serde::Serialize for RequiresPython { impl<'de> serde::Deserialize<'de> for RequiresPython { fn deserialize>(deserializer: D) -> Result { let specifiers = VersionSpecifiers::deserialize(deserializer)?; - let (lower_bound, upper_bound) = - crate::pubgrub::PubGrubSpecifier::from_release_specifiers(&specifiers) - .map_err(serde::de::Error::custom)? - .bounding_range() - .map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned())) - .unwrap_or((Bound::Unbounded, Bound::Unbounded)); + let (lower_bound, upper_bound) = PubGrubSpecifier::from_release_specifiers(&specifiers) + .map_err(serde::de::Error::custom)? + .bounding_range() + .map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned())) + .unwrap_or((Bound::Unbounded, Bound::Unbounded)); Ok(Self { specifiers, range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)), @@ -571,13 +565,11 @@ impl LowerBound { /// Return the [`LowerBound`] truncated to the major and minor version. fn major_minor(&self) -> Self { match &self.0 { - // Ex) `>=3.10.1` -> `>=3.10` (and `>=3.10.0` is `>=3.10`) + // Ex) `>=3.10.1` -> `>=3.10` Bound::Included(version) => Self(Bound::Included(Version::new( version.release().iter().take(2), ))), // Ex) `>3.10.1` -> `>=3.10`. - // This is unintuitive, but `>3.10.1` does indicate that _some_ version of Python 3.10 - // is supported. Bound::Excluded(version) => Self(Bound::Included(Version::new( version.release().iter().take(2), ))), @@ -686,24 +678,20 @@ impl UpperBound { /// Return the [`UpperBound`] truncated to the major and minor version. fn major_minor(&self) -> Self { match &self.0 { - // Ex) `<=3.10.1` -> `<3.11` (but `<=3.10.0` is `<=3.10`) - Bound::Included(version) => { - let major = version.release().first().copied().unwrap_or(3); - let minor = version.release().get(1).copied().unwrap_or(0); - if version.release().get(2).is_some_and(|patch| *patch > 0) { - Self(Bound::Excluded(Version::new([major, minor + 1]))) - } else { - Self(Bound::Included(Version::new([major, minor]))) - } - } - // Ex) `<3.10.1` -> `<3.11` (but `<3.10.0` is `<3.10`) + // Ex) `<=3.10.1` -> `<=3.10` + Bound::Included(version) => Self(Bound::Included(Version::new( + version.release().iter().take(2), + ))), + // Ex) `<3.10.1` -> `<=3.10` (but `<3.10.0` is `<3.10`) Bound::Excluded(version) => { - let major = version.release().first().copied().unwrap_or(3); - let minor = version.release().get(1).copied().unwrap_or(0); if version.release().get(2).is_some_and(|patch| *patch > 0) { - Self(Bound::Excluded(Version::new([major, minor + 1]))) + Self(Bound::Included(Version::new( + version.release().iter().take(2), + ))) } else { - Self(Bound::Excluded(Version::new([major, minor]))) + Self(Bound::Excluded(Version::new( + version.release().iter().take(2), + ))) } } Bound::Unbounded => Self(Bound::Unbounded), @@ -803,121 +791,4 @@ impl From for Bound { } #[cfg(test)] -mod tests { - use std::cmp::Ordering; - use std::collections::Bound; - use std::str::FromStr; - - use uv_distribution_filename::WheelFilename; - use uv_pep440::{Version, VersionSpecifiers}; - - use crate::requires_python::{LowerBound, UpperBound}; - use crate::RequiresPython; - - #[test] - fn requires_python_included() { - let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap(); - let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); - let wheel_names = &[ - "bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl", - "black-24.4.2-cp310-cp310-win_amd64.whl", - "black-24.4.2-cp310-none-win_amd64.whl", - "cbor2-5.6.4-py3-none-any.whl", - "solace_pubsubplus-1.8.0-py36-none-manylinux_2_12_x86_64.whl", - "torch-1.10.0-py310-none-macosx_10_9_x86_64.whl", - "torch-1.10.0-py37-none-macosx_10_9_x86_64.whl", - "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", - ]; - for wheel_name in wheel_names { - assert!( - requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), - "{wheel_name}" - ); - } - - let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap(); - let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); - let wheel_names = &["dearpygui-1.11.1-cp312-cp312-win_amd64.whl"]; - for wheel_name in wheel_names { - assert!( - requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), - "{wheel_name}" - ); - } - } - - #[test] - fn requires_python_dropped() { - let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap(); - let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); - let wheel_names = &[ - "PySocks-1.7.1-py27-none-any.whl", - "black-24.4.2-cp39-cp39-win_amd64.whl", - "dearpygui-1.11.1-cp312-cp312-win_amd64.whl", - "psutil-6.0.0-cp27-none-win32.whl", - "psutil-6.0.0-cp36-cp36m-win32.whl", - "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", - "torch-1.10.0-cp311-none-macosx_10_9_x86_64.whl", - "torch-1.10.0-cp36-none-macosx_10_9_x86_64.whl", - "torch-1.10.0-py311-none-macosx_10_9_x86_64.whl", - ]; - for wheel_name in wheel_names { - assert!( - !requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), - "{wheel_name}" - ); - } - - let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap(); - let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); - let wheel_names = &["dearpygui-1.11.1-cp310-cp310-win_amd64.whl"]; - for wheel_name in wheel_names { - assert!( - !requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), - "{wheel_name}" - ); - } - } - - #[test] - fn lower_bound_ordering() { - let versions = &[ - // No bound - LowerBound::new(Bound::Unbounded), - // >=3.8 - LowerBound::new(Bound::Included(Version::new([3, 8]))), - // >3.8 - LowerBound::new(Bound::Excluded(Version::new([3, 8]))), - // >=3.8.1 - LowerBound::new(Bound::Included(Version::new([3, 8, 1]))), - // >3.8.1 - LowerBound::new(Bound::Excluded(Version::new([3, 8, 1]))), - ]; - for (i, v1) in versions.iter().enumerate() { - for v2 in &versions[i + 1..] { - assert_eq!(v1.cmp(v2), Ordering::Less, "less: {v1:?}\ngreater: {v2:?}"); - } - } - } - - #[test] - fn upper_bound_ordering() { - let versions = &[ - // <3.8 - UpperBound::new(Bound::Excluded(Version::new([3, 8]))), - // <=3.8 - UpperBound::new(Bound::Included(Version::new([3, 8]))), - // <3.8.1 - UpperBound::new(Bound::Excluded(Version::new([3, 8, 1]))), - // <=3.8.1 - UpperBound::new(Bound::Included(Version::new([3, 8, 1]))), - // No bound - UpperBound::new(Bound::Unbounded), - ]; - for (i, v1) in versions.iter().enumerate() { - for v2 in &versions[i + 1..] { - assert_eq!(v1.cmp(v2), Ordering::Less, "less: {v1:?}\ngreater: {v2:?}"); - } - } - } -} +mod tests; diff --git a/crates/uv-resolver/src/requires_python/tests.rs b/crates/uv-resolver/src/requires_python/tests.rs new file mode 100644 index 000000000..660abd71b --- /dev/null +++ b/crates/uv-resolver/src/requires_python/tests.rs @@ -0,0 +1,158 @@ +use std::cmp::Ordering; +use std::collections::Bound; +use std::str::FromStr; + +use uv_distribution_filename::WheelFilename; +use uv_pep440::{Version, VersionSpecifiers}; + +use crate::requires_python::{LowerBound, UpperBound}; +use crate::RequiresPython; + +#[test] +fn requires_python_included() { + let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap(); + let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); + let wheel_names = &[ + "bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl", + "black-24.4.2-cp310-cp310-win_amd64.whl", + "black-24.4.2-cp310-none-win_amd64.whl", + "cbor2-5.6.4-py3-none-any.whl", + "solace_pubsubplus-1.8.0-py36-none-manylinux_2_12_x86_64.whl", + "torch-1.10.0-py310-none-macosx_10_9_x86_64.whl", + "torch-1.10.0-py37-none-macosx_10_9_x86_64.whl", + "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", + ]; + for wheel_name in wheel_names { + assert!( + requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), + "{wheel_name}" + ); + } + + let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap(); + let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); + let wheel_names = &["dearpygui-1.11.1-cp312-cp312-win_amd64.whl"]; + for wheel_name in wheel_names { + assert!( + requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), + "{wheel_name}" + ); + } + + let version_specifiers = VersionSpecifiers::from_str("==3.12.6").unwrap(); + let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); + let wheel_names = &["lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl"]; + for wheel_name in wheel_names { + assert!( + requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), + "{wheel_name}" + ); + } + + let version_specifiers = VersionSpecifiers::from_str("==3.12").unwrap(); + let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); + let wheel_names = &["lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl"]; + for wheel_name in wheel_names { + assert!( + requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), + "{wheel_name}" + ); + } +} + +#[test] +fn requires_python_dropped() { + let version_specifiers = VersionSpecifiers::from_str("==3.10.*").unwrap(); + let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); + let wheel_names = &[ + "PySocks-1.7.1-py27-none-any.whl", + "black-24.4.2-cp39-cp39-win_amd64.whl", + "dearpygui-1.11.1-cp312-cp312-win_amd64.whl", + "psutil-6.0.0-cp27-none-win32.whl", + "psutil-6.0.0-cp36-cp36m-win32.whl", + "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", + "torch-1.10.0-cp311-none-macosx_10_9_x86_64.whl", + "torch-1.10.0-cp36-none-macosx_10_9_x86_64.whl", + "torch-1.10.0-py311-none-macosx_10_9_x86_64.whl", + ]; + for wheel_name in wheel_names { + assert!( + !requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), + "{wheel_name}" + ); + } + + let version_specifiers = VersionSpecifiers::from_str(">=3.12.3").unwrap(); + let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); + let wheel_names = &["dearpygui-1.11.1-cp310-cp310-win_amd64.whl"]; + for wheel_name in wheel_names { + assert!( + !requires_python.matches_wheel_tag(&WheelFilename::from_str(wheel_name).unwrap()), + "{wheel_name}" + ); + } +} + +#[test] +fn lower_bound_ordering() { + let versions = &[ + // No bound + LowerBound::new(Bound::Unbounded), + // >=3.8 + LowerBound::new(Bound::Included(Version::new([3, 8]))), + // >3.8 + LowerBound::new(Bound::Excluded(Version::new([3, 8]))), + // >=3.8.1 + LowerBound::new(Bound::Included(Version::new([3, 8, 1]))), + // >3.8.1 + LowerBound::new(Bound::Excluded(Version::new([3, 8, 1]))), + ]; + for (i, v1) in versions.iter().enumerate() { + for v2 in &versions[i + 1..] { + assert_eq!(v1.cmp(v2), Ordering::Less, "less: {v1:?}\ngreater: {v2:?}"); + } + } +} + +#[test] +fn upper_bound_ordering() { + let versions = &[ + // <3.8 + UpperBound::new(Bound::Excluded(Version::new([3, 8]))), + // <=3.8 + UpperBound::new(Bound::Included(Version::new([3, 8]))), + // <3.8.1 + UpperBound::new(Bound::Excluded(Version::new([3, 8, 1]))), + // <=3.8.1 + UpperBound::new(Bound::Included(Version::new([3, 8, 1]))), + // No bound + UpperBound::new(Bound::Unbounded), + ]; + for (i, v1) in versions.iter().enumerate() { + for v2 in &versions[i + 1..] { + assert_eq!(v1.cmp(v2), Ordering::Less, "less: {v1:?}\ngreater: {v2:?}"); + } + } +} + +#[test] +fn is_exact_without_patch() { + let test_cases = [ + ("==3.12", true), + ("==3.10, <3.11", true), + ("==3.10, <=3.11", true), + ("==3.12.1", false), + ("==3.12.*", false), + ("==3.*", false), + (">=3.10", false), + (">3.9", false), + ("<4.0", false), + (">=3.10, <3.11", false), + ("", false), + ]; + for (version, expected) in test_cases { + let version_specifiers = VersionSpecifiers::from_str(version).unwrap(); + let requires_python = RequiresPython::from_specifiers(&version_specifiers).unwrap(); + assert_eq!(requires_python.is_exact_without_patch(), expected); + } +} diff --git a/crates/uv-resolver/src/resolution/graph.rs b/crates/uv-resolver/src/resolution/graph.rs index f8d3a8fe9..ff8a540de 100644 --- a/crates/uv-resolver/src/resolution/graph.rs +++ b/crates/uv-resolver/src/resolution/graph.rs @@ -10,7 +10,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use uv_configuration::{Constraints, Overrides}; use uv_distribution::Metadata; use uv_distribution_types::{ - Dist, DistributionMetadata, Name, ResolutionDiagnostic, ResolvedDist, VersionId, + Dist, DistributionMetadata, IndexUrl, Name, ResolutionDiagnostic, ResolvedDist, VersionId, VersionOrUrlRef, }; use uv_git::GitResolver; @@ -88,6 +88,7 @@ struct PackageRef<'a> { package_name: &'a PackageName, version: &'a Version, url: Option<&'a VerbatimParsedUrl>, + index: Option<&'a IndexUrl>, extra: Option<&'a ExtraName>, group: Option<&'a GroupName>, } @@ -284,6 +285,7 @@ impl ResolutionGraph { package_name: from, version: &edge.from_version, url: edge.from_url.as_ref(), + index: edge.from_index.as_ref(), extra: edge.from_extra.as_ref(), group: edge.from_dev.as_ref(), }] @@ -292,6 +294,7 @@ impl ResolutionGraph { package_name: &edge.to, version: &edge.to_version, url: edge.to_url.as_ref(), + index: edge.to_index.as_ref(), extra: edge.to_extra.as_ref(), group: edge.to_dev.as_ref(), }]; @@ -320,7 +323,7 @@ impl ResolutionGraph { diagnostics: &mut Vec, preferences: &Preferences, pins: &FilePins, - index: &InMemoryIndex, + in_memory: &InMemoryIndex, git: &GitResolver, package: &'a ResolutionPackage, version: &'a Version, @@ -330,16 +333,18 @@ impl ResolutionGraph { extra, dev, url, + index, } = &package; // Map the package to a distribution. let (dist, hashes, metadata) = Self::parse_dist( name, + index.as_ref(), url.as_ref(), version, pins, diagnostics, preferences, - index, + in_memory, git, )?; @@ -366,7 +371,7 @@ impl ResolutionGraph { } // Add the distribution to the graph. - let index = petgraph.add_node(ResolutionGraphNode::Dist(AnnotatedDist { + let node = petgraph.add_node(ResolutionGraphNode::Dist(AnnotatedDist { dist, name: name.clone(), version: version.clone(), @@ -381,22 +386,24 @@ impl ResolutionGraph { package_name: name, version, url: url.as_ref(), + index: index.as_ref(), extra: extra.as_ref(), group: dev.as_ref(), }, - index, + node, ); Ok(()) } fn parse_dist( name: &PackageName, + index: Option<&IndexUrl>, url: Option<&VerbatimParsedUrl>, version: &Version, pins: &FilePins, diagnostics: &mut Vec, preferences: &Preferences, - index: &InMemoryIndex, + in_memory: &InMemoryIndex, git: &GitResolver, ) -> Result<(ResolvedDist, Vec, Option), ResolveError> { Ok(if let Some(url) = url { @@ -406,14 +413,24 @@ impl ResolutionGraph { let version_id = VersionId::from_url(&url.verbatim); // Extract the hashes. - let hashes = - Self::get_hashes(name, Some(url), &version_id, version, preferences, index); + let hashes = Self::get_hashes( + name, + index, + Some(url), + &version_id, + version, + preferences, + in_memory, + ); // Extract the metadata. let metadata = { - let response = index.distributions().get(&version_id).unwrap_or_else(|| { - panic!("Every URL distribution should have metadata: {version_id:?}") - }); + let response = in_memory + .distributions() + .get(&version_id) + .unwrap_or_else(|| { + panic!("Every URL distribution should have metadata: {version_id:?}") + }); let MetadataResponse::Found(archive) = &*response else { panic!("Every URL distribution should have metadata: {version_id:?}") @@ -449,17 +466,28 @@ impl ResolutionGraph { } // Extract the hashes. - let hashes = Self::get_hashes(name, None, &version_id, version, preferences, index); + let hashes = Self::get_hashes( + name, + index, + None, + &version_id, + version, + preferences, + in_memory, + ); // Extract the metadata. let metadata = { - index.distributions().get(&version_id).and_then(|response| { - if let MetadataResponse::Found(archive) = &*response { - Some(archive.metadata.clone()) - } else { - None - } - }) + in_memory + .distributions() + .get(&version_id) + .and_then(|response| { + if let MetadataResponse::Found(archive) = &*response { + Some(archive.metadata.clone()) + } else { + None + } + }) }; (dist, hashes, metadata) @@ -470,11 +498,12 @@ impl ResolutionGraph { /// lockfile. fn get_hashes( name: &PackageName, + index: Option<&IndexUrl>, url: Option<&VerbatimParsedUrl>, version_id: &VersionId, version: &Version, preferences: &Preferences, - index: &InMemoryIndex, + in_memory: &InMemoryIndex, ) -> Vec { // 1. Look for hashes from the lockfile. if let Some(digests) = preferences.match_hashes(name, version) { @@ -484,7 +513,7 @@ impl ResolutionGraph { } // 2. Look for hashes for the distribution (i.e., the specific wheel or source distribution). - if let Some(metadata_response) = index.distributions().get(version_id) { + if let Some(metadata_response) = in_memory.distributions().get(version_id) { if let MetadataResponse::Found(ref archive) = *metadata_response { let mut digests = archive.hashes.clone(); digests.sort_unstable(); @@ -496,7 +525,13 @@ impl ResolutionGraph { // 3. Look for hashes from the registry, which are served at the package level. if url.is_none() { - if let Some(versions_response) = index.packages().get(name) { + let versions_response = if let Some(index) = index { + in_memory.explicit().get(&(name.clone(), index.clone())) + } else { + in_memory.implicit().get(name) + }; + + if let Some(versions_response) = versions_response { if let VersionsResponse::Found(ref version_maps) = *versions_response { if let Some(digests) = version_maps .iter() diff --git a/crates/uv-resolver/src/resolver/batch_prefetch.rs b/crates/uv-resolver/src/resolver/batch_prefetch.rs index 7cf9684fd..586a5e3be 100644 --- a/crates/uv-resolver/src/resolver/batch_prefetch.rs +++ b/crates/uv-resolver/src/resolver/batch_prefetch.rs @@ -6,13 +6,12 @@ use rustc_hash::FxHashMap; use tokio::sync::mpsc::Sender; use tracing::{debug, trace}; -use uv_distribution_types::{CompatibleDist, DistributionMetadata, IndexCapabilities}; -use uv_pep440::Version; - use crate::candidate_selector::CandidateSelector; use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner}; use crate::resolver::Request; use crate::{InMemoryIndex, PythonRequirement, ResolveError, ResolverMarkers, VersionsResponse}; +use uv_distribution_types::{CompatibleDist, DistributionMetadata, IndexCapabilities, IndexUrl}; +use uv_pep440::Version; enum BatchPrefetchStrategy { /// Go through the next versions assuming the existing selection and its constraints @@ -47,11 +46,12 @@ impl BatchPrefetcher { pub(crate) fn prefetch_batches( &mut self, next: &PubGrubPackage, + index: Option<&IndexUrl>, version: &Version, current_range: &Range, python_requirement: &PythonRequirement, request_sink: &Sender, - index: &InMemoryIndex, + in_memory: &InMemoryIndex, capabilities: &IndexCapabilities, selector: &CandidateSelector, markers: &ResolverMarkers, @@ -73,10 +73,17 @@ impl BatchPrefetcher { let total_prefetch = min(num_tried, 50); // This is immediate, we already fetched the version map. - let versions_response = index - .packages() - .wait_blocking(name) - .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?; + let versions_response = if let Some(index) = index { + in_memory + .explicit() + .wait_blocking(&(name.clone(), index.clone())) + .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))? + } else { + in_memory + .implicit() + .wait_blocking(name) + .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))? + }; let VersionsResponse::Found(ref version_map) = *versions_response else { return Ok(()); @@ -191,7 +198,7 @@ impl BatchPrefetcher { ); prefetch_count += 1; - if index.distributions().register(candidate.version_id()) { + if in_memory.distributions().register(candidate.version_id()) { let request = Request::from(dist); request_sink.blocking_send(request)?; } diff --git a/crates/uv-resolver/src/resolver/fork_map.rs b/crates/uv-resolver/src/resolver/fork_map.rs index 47003267d..fa9fd1666 100644 --- a/crates/uv-resolver/src/resolver/fork_map.rs +++ b/crates/uv-resolver/src/resolver/fork_map.rs @@ -44,6 +44,11 @@ impl ForkMap { !self.get(package_name, markers).is_empty() } + /// Returns `true` if the map contains any values for a package. + pub(crate) fn contains_key(&self, package_name: &PackageName) -> bool { + self.0.contains_key(package_name) + } + /// Returns a list of values associated with a package that are compatible with the given fork. /// /// Compatibility implies that the markers on the requirement that contained this value diff --git a/crates/uv-resolver/src/resolver/index.rs b/crates/uv-resolver/src/resolver/index.rs index 9796c7769..222a93708 100644 --- a/crates/uv-resolver/src/resolver/index.rs +++ b/crates/uv-resolver/src/resolver/index.rs @@ -2,7 +2,7 @@ use std::hash::BuildHasherDefault; use std::sync::Arc; use rustc_hash::FxHasher; -use uv_distribution_types::VersionId; +use uv_distribution_types::{IndexUrl, VersionId}; use uv_normalize::PackageName; use uv_once_map::OnceMap; @@ -16,7 +16,9 @@ pub struct InMemoryIndex(Arc); struct SharedInMemoryIndex { /// A map from package name to the metadata for that package and the index where the metadata /// came from. - packages: FxOnceMap>, + implicit: FxOnceMap>, + + explicit: FxOnceMap<(PackageName, IndexUrl), Arc>, /// A map from package ID to metadata for that distribution. distributions: FxOnceMap>, @@ -26,8 +28,13 @@ pub(crate) type FxOnceMap = OnceMap>; impl InMemoryIndex { /// Returns a reference to the package metadata map. - pub fn packages(&self) -> &FxOnceMap> { - &self.0.packages + pub fn implicit(&self) -> &FxOnceMap> { + &self.0.implicit + } + + /// Returns a reference to the package metadata map. + pub fn explicit(&self) -> &FxOnceMap<(PackageName, IndexUrl), Arc> { + &self.0.explicit } /// Returns a reference to the distribution metadata map. diff --git a/crates/uv-resolver/src/resolver/indexes.rs b/crates/uv-resolver/src/resolver/indexes.rs new file mode 100644 index 000000000..556a4f444 --- /dev/null +++ b/crates/uv-resolver/src/resolver/indexes.rs @@ -0,0 +1,61 @@ +use uv_distribution_types::IndexUrl; +use uv_normalize::PackageName; +use uv_pep508::VerbatimUrl; +use uv_pypi_types::RequirementSource; + +use crate::resolver::ForkMap; +use crate::{DependencyMode, Manifest, ResolverMarkers}; + +/// A map of package names to their explicit index. +/// +/// For example, given: +/// ```toml +/// [[tool.uv.index]] +/// name = "pytorch" +/// url = "https://download.pytorch.org/whl/cu121" +/// +/// [tool.uv.sources] +/// torch = { index = "pytorch" } +/// ``` +/// +/// [`Indexes`] would contain a single entry mapping `torch` to `https://download.pytorch.org/whl/cu121`. +#[derive(Debug, Default, Clone)] +pub(crate) struct Indexes(ForkMap); + +impl Indexes { + /// Determine the set of explicit, pinned indexes in the [`Manifest`]. + pub(crate) fn from_manifest( + manifest: &Manifest, + markers: &ResolverMarkers, + dependencies: DependencyMode, + ) -> Self { + let mut indexes = ForkMap::default(); + + for requirement in manifest.requirements(markers, dependencies) { + let RequirementSource::Registry { + index: Some(index), .. + } = &requirement.source + else { + continue; + }; + let index = IndexUrl::from(VerbatimUrl::from_url(index.clone())); + indexes.add(&requirement, index); + } + + Self(indexes) + } + + /// Returns `true` if the map contains any indexes for a package. + pub(crate) fn contains_key(&self, name: &PackageName) -> bool { + self.0.contains_key(name) + } + + /// Return the explicit index used for a package in the given fork. + pub(crate) fn get( + &self, + package_name: &PackageName, + markers: &ResolverMarkers, + ) -> Vec<&IndexUrl> { + self.0.get(package_name, markers) + } +} diff --git a/crates/uv-resolver/src/resolver/locals.rs b/crates/uv-resolver/src/resolver/locals.rs index c570c9ad3..dedd9ad9b 100644 --- a/crates/uv-resolver/src/resolver/locals.rs +++ b/crates/uv-resolver/src/resolver/locals.rs @@ -198,136 +198,4 @@ pub(crate) fn from_source(source: &RequirementSource) -> Option { } #[cfg(test)] -mod tests { - use std::str::FromStr; - - use anyhow::Result; - use url::Url; - - use uv_pep440::{Operator, Version, VersionSpecifier, VersionSpecifiers}; - use uv_pep508::VerbatimUrl; - use uv_pypi_types::ParsedUrl; - use uv_pypi_types::RequirementSource; - - use super::{from_source, Locals}; - - #[test] - fn extract_locals() -> Result<()> { - // Extract from a source distribution in a URL. - let url = VerbatimUrl::from_url(Url::parse("https://example.com/foo-1.0.0+local.tar.gz")?); - let source = - RequirementSource::from_parsed_url(ParsedUrl::try_from(url.to_url()).unwrap(), url); - let locals: Vec<_> = from_source(&source).into_iter().collect(); - assert_eq!(locals, vec![Version::from_str("1.0.0+local")?]); - - // Extract from a wheel in a URL. - let url = VerbatimUrl::from_url(Url::parse( - "https://example.com/foo-1.0.0+local-cp39-cp39-linux_x86_64.whl", - )?); - let source = - RequirementSource::from_parsed_url(ParsedUrl::try_from(url.to_url()).unwrap(), url); - let locals: Vec<_> = from_source(&source).into_iter().collect(); - assert_eq!(locals, vec![Version::from_str("1.0.0+local")?]); - - // Don't extract anything if the URL is opaque. - let url = VerbatimUrl::from_url(Url::parse("git+https://example.com/foo/bar")?); - let source = - RequirementSource::from_parsed_url(ParsedUrl::try_from(url.to_url()).unwrap(), url); - let locals: Vec<_> = from_source(&source).into_iter().collect(); - assert!(locals.is_empty()); - - // Extract from `==` specifiers. - let version = VersionSpecifiers::from_iter([ - VersionSpecifier::from_version(Operator::GreaterThan, Version::from_str("1.0.0")?)?, - VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)?, - ]); - let source = RequirementSource::Registry { - specifier: version, - index: None, - }; - let locals: Vec<_> = from_source(&source).into_iter().collect(); - assert_eq!(locals, vec![Version::from_str("1.0.0+local")?]); - - // Ignore other specifiers. - let version = VersionSpecifiers::from_iter([VersionSpecifier::from_version( - Operator::NotEqual, - Version::from_str("1.0.0+local")?, - )?]); - let source = RequirementSource::Registry { - specifier: version, - index: None, - }; - let locals: Vec<_> = from_source(&source).into_iter().collect(); - assert!(locals.is_empty()); - - Ok(()) - } - - #[test] - fn map_version() -> Result<()> { - // Given `==1.0.0`, if the local version is `1.0.0+local`, map to `==1.0.0+local`. - let local = Version::from_str("1.0.0+local")?; - let specifier = - VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0")?)?; - assert_eq!( - Locals::map(&local, &specifier)?, - VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)? - ); - - // Given `!=1.0.0`, if the local version is `1.0.0+local`, map to `!=1.0.0+local`. - let local = Version::from_str("1.0.0+local")?; - let specifier = - VersionSpecifier::from_version(Operator::NotEqual, Version::from_str("1.0.0")?)?; - assert_eq!( - Locals::map(&local, &specifier)?, - VersionSpecifier::from_version(Operator::NotEqual, Version::from_str("1.0.0+local")?)? - ); - - // Given `<=1.0.0`, if the local version is `1.0.0+local`, map to `==1.0.0+local`. - let local = Version::from_str("1.0.0+local")?; - let specifier = - VersionSpecifier::from_version(Operator::LessThanEqual, Version::from_str("1.0.0")?)?; - assert_eq!( - Locals::map(&local, &specifier)?, - VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)? - ); - - // Given `>1.0.0`, `1.0.0+local` is already (correctly) disallowed. - let local = Version::from_str("1.0.0+local")?; - let specifier = - VersionSpecifier::from_version(Operator::GreaterThan, Version::from_str("1.0.0")?)?; - assert_eq!( - Locals::map(&local, &specifier)?, - VersionSpecifier::from_version(Operator::GreaterThan, Version::from_str("1.0.0")?)? - ); - - // Given `===1.0.0`, `1.0.0+local` is already (correctly) disallowed. - let local = Version::from_str("1.0.0+local")?; - let specifier = - VersionSpecifier::from_version(Operator::ExactEqual, Version::from_str("1.0.0")?)?; - assert_eq!( - Locals::map(&local, &specifier)?, - VersionSpecifier::from_version(Operator::ExactEqual, Version::from_str("1.0.0")?)? - ); - - // Given `==1.0.0+local`, `1.0.0+local` is already (correctly) allowed. - let local = Version::from_str("1.0.0+local")?; - let specifier = - VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)?; - assert_eq!( - Locals::map(&local, &specifier)?, - VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)? - ); - - // Given `==1.0.0+other`, `1.0.0+local` is already (correctly) disallowed. - let local = Version::from_str("1.0.0+local")?; - let specifier = - VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+other")?)?; - assert_eq!( - Locals::map(&local, &specifier)?, - VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+other")?)? - ); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-resolver/src/resolver/locals/tests.rs b/crates/uv-resolver/src/resolver/locals/tests.rs new file mode 100644 index 000000000..7ff7bfe3d --- /dev/null +++ b/crates/uv-resolver/src/resolver/locals/tests.rs @@ -0,0 +1,130 @@ +use std::str::FromStr; + +use anyhow::Result; +use url::Url; + +use uv_pep440::{Operator, Version, VersionSpecifier, VersionSpecifiers}; +use uv_pep508::VerbatimUrl; +use uv_pypi_types::ParsedUrl; +use uv_pypi_types::RequirementSource; + +use super::{from_source, Locals}; + +#[test] +fn extract_locals() -> Result<()> { + // Extract from a source distribution in a URL. + let url = VerbatimUrl::from_url(Url::parse("https://example.com/foo-1.0.0+local.tar.gz")?); + let source = + RequirementSource::from_parsed_url(ParsedUrl::try_from(url.to_url()).unwrap(), url); + let locals: Vec<_> = from_source(&source).into_iter().collect(); + assert_eq!(locals, vec![Version::from_str("1.0.0+local")?]); + + // Extract from a wheel in a URL. + let url = VerbatimUrl::from_url(Url::parse( + "https://example.com/foo-1.0.0+local-cp39-cp39-linux_x86_64.whl", + )?); + let source = + RequirementSource::from_parsed_url(ParsedUrl::try_from(url.to_url()).unwrap(), url); + let locals: Vec<_> = from_source(&source).into_iter().collect(); + assert_eq!(locals, vec![Version::from_str("1.0.0+local")?]); + + // Don't extract anything if the URL is opaque. + let url = VerbatimUrl::from_url(Url::parse("git+https://example.com/foo/bar")?); + let source = + RequirementSource::from_parsed_url(ParsedUrl::try_from(url.to_url()).unwrap(), url); + let locals: Vec<_> = from_source(&source).into_iter().collect(); + assert!(locals.is_empty()); + + // Extract from `==` specifiers. + let version = VersionSpecifiers::from_iter([ + VersionSpecifier::from_version(Operator::GreaterThan, Version::from_str("1.0.0")?)?, + VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)?, + ]); + let source = RequirementSource::Registry { + specifier: version, + index: None, + }; + let locals: Vec<_> = from_source(&source).into_iter().collect(); + assert_eq!(locals, vec![Version::from_str("1.0.0+local")?]); + + // Ignore other specifiers. + let version = VersionSpecifiers::from_iter([VersionSpecifier::from_version( + Operator::NotEqual, + Version::from_str("1.0.0+local")?, + )?]); + let source = RequirementSource::Registry { + specifier: version, + index: None, + }; + let locals: Vec<_> = from_source(&source).into_iter().collect(); + assert!(locals.is_empty()); + + Ok(()) +} + +#[test] +fn map_version() -> Result<()> { + // Given `==1.0.0`, if the local version is `1.0.0+local`, map to `==1.0.0+local`. + let local = Version::from_str("1.0.0+local")?; + let specifier = VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0")?)?; + assert_eq!( + Locals::map(&local, &specifier)?, + VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)? + ); + + // Given `!=1.0.0`, if the local version is `1.0.0+local`, map to `!=1.0.0+local`. + let local = Version::from_str("1.0.0+local")?; + let specifier = + VersionSpecifier::from_version(Operator::NotEqual, Version::from_str("1.0.0")?)?; + assert_eq!( + Locals::map(&local, &specifier)?, + VersionSpecifier::from_version(Operator::NotEqual, Version::from_str("1.0.0+local")?)? + ); + + // Given `<=1.0.0`, if the local version is `1.0.0+local`, map to `==1.0.0+local`. + let local = Version::from_str("1.0.0+local")?; + let specifier = + VersionSpecifier::from_version(Operator::LessThanEqual, Version::from_str("1.0.0")?)?; + assert_eq!( + Locals::map(&local, &specifier)?, + VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)? + ); + + // Given `>1.0.0`, `1.0.0+local` is already (correctly) disallowed. + let local = Version::from_str("1.0.0+local")?; + let specifier = + VersionSpecifier::from_version(Operator::GreaterThan, Version::from_str("1.0.0")?)?; + assert_eq!( + Locals::map(&local, &specifier)?, + VersionSpecifier::from_version(Operator::GreaterThan, Version::from_str("1.0.0")?)? + ); + + // Given `===1.0.0`, `1.0.0+local` is already (correctly) disallowed. + let local = Version::from_str("1.0.0+local")?; + let specifier = + VersionSpecifier::from_version(Operator::ExactEqual, Version::from_str("1.0.0")?)?; + assert_eq!( + Locals::map(&local, &specifier)?, + VersionSpecifier::from_version(Operator::ExactEqual, Version::from_str("1.0.0")?)? + ); + + // Given `==1.0.0+local`, `1.0.0+local` is already (correctly) allowed. + let local = Version::from_str("1.0.0+local")?; + let specifier = + VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)?; + assert_eq!( + Locals::map(&local, &specifier)?, + VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+local")?)? + ); + + // Given `==1.0.0+other`, `1.0.0+local` is already (correctly) disallowed. + let local = Version::from_str("1.0.0+local")?; + let specifier = + VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+other")?)?; + assert_eq!( + Locals::map(&local, &specifier)?, + VersionSpecifier::from_version(Operator::Equal, Version::from_str("1.0.0+other")?)? + ); + + Ok(()) +} diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 16a6ea914..4570a5e81 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -28,8 +28,9 @@ use uv_configuration::{Constraints, Overrides}; use uv_distribution::{ArchiveMetadata, DistributionDatabase}; use uv_distribution_types::{ BuiltDist, CompatibleDist, Dist, DistributionMetadata, IncompatibleDist, IncompatibleSource, - IncompatibleWheel, IndexCapabilities, IndexLocations, InstalledDist, PythonRequirementKind, - RemoteSource, ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef, + IncompatibleWheel, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, + PythonRequirementKind, RemoteSource, ResolvedDist, ResolvedDistRef, SourceDist, + VersionOrUrlRef, }; use uv_git::GitResolver; use uv_normalize::{ExtraName, GroupName, PackageName}; @@ -43,6 +44,7 @@ use uv_warnings::warn_user_once; use crate::candidate_selector::{CandidateDist, CandidateSelector}; use crate::dependency_provider::UvDependencyProvider; use crate::error::{NoSolutionError, ResolveError}; +use crate::fork_indexes::ForkIndexes; use crate::fork_urls::ForkUrls; use crate::manifest::Manifest; use crate::pins::FilePins; @@ -60,6 +62,7 @@ pub(crate) use crate::resolver::availability::{ use crate::resolver::batch_prefetch::BatchPrefetcher; use crate::resolver::groups::Groups; pub use crate::resolver::index::InMemoryIndex; +use crate::resolver::indexes::Indexes; pub use crate::resolver::provider::{ DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider, VersionsResponse, WheelMetadataResult, @@ -74,6 +77,7 @@ mod batch_prefetch; mod fork_map; mod groups; mod index; +mod indexes; mod locals; mod provider; mod reporter; @@ -100,6 +104,7 @@ struct ResolverState { exclusions: Exclusions, urls: Urls, locals: Locals, + indexes: Indexes, dependency_mode: DependencyMode, hasher: HashStrategy, markers: ResolverMarkers, @@ -161,6 +166,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider> hasher, options.exclude_newer, build_context.build_options(), + build_context.capabilities(), ); Self::new_custom_io( @@ -172,7 +178,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider> index, build_context.git(), build_context.capabilities(), - build_context.index_locations(), + build_context.locations(), provider, installed_packages, ) @@ -204,6 +210,7 @@ impl dependency_mode: options.dependency_mode, urls: Urls::from_manifest(&manifest, &markers, git, options.dependency_mode)?, locals: Locals::from_manifest(&manifest, &markers, options.dependency_mode), + indexes: Indexes::from_manifest(&manifest, &markers, options.dependency_mode), groups: Groups::from_manifest(&manifest, &markers), project: manifest.project, workspace_members: manifest.workspace_members, @@ -329,9 +336,11 @@ impl ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState, + index: Option<&IndexUrl>, request_sink: &Sender, ) -> Result<(), ResolveError> { // Ignore unresolved URL packages. @@ -732,13 +757,14 @@ impl ResolverState, + index: Option<&IndexUrl>, request_sink: &Sender, ) -> Result<(), ResolveError> { // Only request real package @@ -757,10 +783,19 @@ impl ResolverState ResolverState( packages: impl Iterator)>, urls: &Urls, + indexes: &Indexes, python_requirement: &PythonRequirement, request_sink: &Sender, ) -> Result<(), ResolveError> { @@ -791,6 +827,10 @@ impl ResolverState ResolverState, range: &Range, pins: &mut FilePins, preferences: &Preferences, @@ -838,6 +879,7 @@ impl ResolverState ResolverState, range: &Range, package: &PubGrubPackage, preferences: &Preferences, @@ -963,11 +1006,17 @@ impl ResolverState, ) -> Result, ResolveError> { // Wait for the metadata to be available. - let versions_response = self - .index - .packages() - .wait_blocking(name) - .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?; + let versions_response = if let Some(index) = index { + self.index + .explicit() + .wait_blocking(&(name.clone(), index.clone())) + .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))? + } else { + self.index + .implicit() + .wait_blocking(name) + .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))? + }; visited.insert(name.clone()); let version_maps = match *versions_response { @@ -1643,11 +1692,15 @@ impl ResolverState { - trace!("Received package metadata for: {package_name}"); - self.index - .packages() - .done(package_name, Arc::new(version_map)); + Some(Response::Package(name, index, version_map)) => { + trace!("Received package metadata for: {name}"); + if let Some(index) = index { + self.index + .explicit() + .done((name, index), Arc::new(version_map)); + } else { + self.index.implicit().done(name, Arc::new(version_map)); + } } Some(Response::Installed { dist, metadata }) => { trace!("Received installed distribution metadata for: {dist}"); @@ -1709,14 +1762,18 @@ impl ResolverState Result, ResolveError> { match request { // Fetch package metadata from the registry. - Request::Package(package_name) => { + Request::Package(package_name, index) => { let package_versions = provider - .get_package_versions(&package_name) + .get_package_versions(&package_name, index.as_ref()) .boxed_local() .await .map_err(ResolveError::Client)?; - Ok(Some(Response::Package(package_name, package_versions))) + Ok(Some(Response::Package( + package_name, + index, + package_versions, + ))) } // Fetch distribution metadata from the distribution database. @@ -1760,7 +1817,7 @@ impl ResolverState ResolverState, fork_urls: ForkUrls, + fork_indexes: &ForkIndexes, markers: ResolverMarkers, visited: &FxHashSet, index_locations: &IndexLocations, + index_capabilities: &IndexCapabilities, ) -> ResolveError { err = NoSolutionError::collapse_proxies(err); @@ -1954,7 +2013,12 @@ impl ResolverState ResolverState, version: &Version, urls: &Urls, + indexes: &Indexes, locals: &Locals, mut dependencies: Vec, git: &GitResolver, @@ -2158,6 +2231,11 @@ impl ForkState { *version = version.union(&local); } } + + // If the package is pinned to an exact index, add it to the fork. + for index in indexes.get(name, &self.markers) { + self.fork_indexes.insert(name, index, &self.markers)?; + } } if let Some(for_package) = for_package { @@ -2307,6 +2385,9 @@ impl ForkState { _ => continue, }; let self_url = self_name.as_ref().and_then(|name| self.fork_urls.get(name)); + let self_index = self_name + .as_ref() + .and_then(|name| self.fork_indexes.get(name)); match **dependency_package { PubGrubPackageInner::Package { @@ -2319,15 +2400,18 @@ impl ForkState { continue; } let to_url = self.fork_urls.get(dependency_name); + let to_index = self.fork_indexes.get(dependency_name); let edge = ResolutionDependencyEdge { from: self_name.cloned(), from_version: self_version.clone(), from_url: self_url.cloned(), + from_index: self_index.cloned(), from_extra: self_extra.cloned(), from_dev: self_dev.cloned(), to: dependency_name.clone(), to_version: dependency_version.clone(), to_url: to_url.cloned(), + to_index: to_index.cloned(), to_extra: dependency_extra.clone(), to_dev: dependency_dev.clone(), marker: MarkerTree::TRUE, @@ -2344,15 +2428,18 @@ impl ForkState { continue; } let to_url = self.fork_urls.get(dependency_name); + let to_index = self.fork_indexes.get(dependency_name); let edge = ResolutionDependencyEdge { from: self_name.cloned(), from_version: self_version.clone(), from_url: self_url.cloned(), + from_index: self_index.cloned(), from_extra: self_extra.cloned(), from_dev: self_dev.cloned(), to: dependency_name.clone(), to_version: dependency_version.clone(), to_url: to_url.cloned(), + to_index: to_index.cloned(), to_extra: None, to_dev: None, marker: dependency_marker.clone(), @@ -2370,15 +2457,18 @@ impl ForkState { continue; } let to_url = self.fork_urls.get(dependency_name); + let to_index = self.fork_indexes.get(dependency_name); let edge = ResolutionDependencyEdge { from: self_name.cloned(), from_version: self_version.clone(), from_url: self_url.cloned(), + from_index: self_index.cloned(), from_extra: self_extra.cloned(), from_dev: self_dev.cloned(), to: dependency_name.clone(), to_version: dependency_version.clone(), to_url: to_url.cloned(), + to_index: to_index.cloned(), to_extra: Some(dependency_extra.clone()), to_dev: None, marker: MarkerTree::from(dependency_marker.clone()), @@ -2396,15 +2486,18 @@ impl ForkState { continue; } let to_url = self.fork_urls.get(dependency_name); + let to_index = self.fork_indexes.get(dependency_name); let edge = ResolutionDependencyEdge { from: self_name.cloned(), from_version: self_version.clone(), from_url: self_url.cloned(), + from_index: self_index.cloned(), from_extra: self_extra.cloned(), from_dev: self_dev.cloned(), to: dependency_name.clone(), to_version: dependency_version.clone(), to_url: to_url.cloned(), + to_index: to_index.cloned(), to_extra: None, to_dev: Some(dependency_dev.clone()), marker: MarkerTree::from(dependency_marker.clone()), @@ -2433,6 +2526,7 @@ impl ForkState { extra: extra.clone(), dev: dev.clone(), url: self.fork_urls.get(name).cloned(), + index: self.fork_indexes.get(name).cloned(), }, version, )) @@ -2473,6 +2567,9 @@ pub(crate) struct ResolutionPackage { pub(crate) dev: Option, /// For index packages, this is `None`. pub(crate) url: Option, + /// For URL packages, this is `None`, and is only `Some` for packages that are pinned to a + /// specific index via `tool.uv.sources`. + pub(crate) index: Option, } /// The `from_` fields and the `to_` fields allow mapping to the originating and target @@ -2483,11 +2580,13 @@ pub(crate) struct ResolutionDependencyEdge { pub(crate) from: Option, pub(crate) from_version: Version, pub(crate) from_url: Option, + pub(crate) from_index: Option, pub(crate) from_extra: Option, pub(crate) from_dev: Option, pub(crate) to: PackageName, pub(crate) to_version: Version, pub(crate) to_url: Option, + pub(crate) to_index: Option, pub(crate) to_extra: Option, pub(crate) to_dev: Option, pub(crate) marker: MarkerTree, @@ -2504,7 +2603,7 @@ impl ResolutionPackage { #[allow(clippy::large_enum_variant)] pub(crate) enum Request { /// A request to fetch the metadata for a package. - Package(PackageName), + Package(PackageName, Option), /// A request to fetch the metadata for a built or source distribution. Dist(Dist), /// A request to fetch the metadata from an already-installed distribution. @@ -2553,7 +2652,7 @@ impl<'a> From> for Request { impl Display for Request { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::Package(package_name) => { + Self::Package(package_name, _) => { write!(f, "Versions {package_name}") } Self::Dist(dist) => { @@ -2573,7 +2672,7 @@ impl Display for Request { #[allow(clippy::large_enum_variant)] enum Response { /// The returned metadata for a package hosted on a registry. - Package(PackageName, VersionsResponse), + Package(PackageName, Option, VersionsResponse), /// The returned metadata for a distribution. Dist { dist: Dist, diff --git a/crates/uv-resolver/src/resolver/provider.rs b/crates/uv-resolver/src/resolver/provider.rs index 7823dffa1..66729e4a8 100644 --- a/crates/uv-resolver/src/resolver/provider.rs +++ b/crates/uv-resolver/src/resolver/provider.rs @@ -2,7 +2,7 @@ use std::future::Future; use uv_configuration::BuildOptions; use uv_distribution::{ArchiveMetadata, DistributionDatabase}; -use uv_distribution_types::Dist; +use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl}; use uv_normalize::PackageName; use uv_platform_tags::Tags; use uv_types::{BuildContext, HashStrategy}; @@ -49,6 +49,7 @@ pub trait ResolverProvider { fn get_package_versions<'io>( &'io self, package_name: &'io PackageName, + index: Option<&'io IndexUrl>, ) -> impl Future + 'io; /// Get the metadata for a distribution. @@ -79,6 +80,7 @@ pub struct DefaultResolverProvider<'a, Context: BuildContext> { hasher: HashStrategy, exclude_newer: Option, build_options: &'a BuildOptions, + capabilities: &'a IndexCapabilities, } impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> { @@ -92,6 +94,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> { hasher: &'a HashStrategy, exclude_newer: Option, build_options: &'a BuildOptions, + capabilities: &'a IndexCapabilities, ) -> Self { Self { fetcher, @@ -102,6 +105,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> { hasher: hasher.clone(), exclude_newer, build_options, + capabilities, } } } @@ -111,11 +115,12 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a, async fn get_package_versions<'io>( &'io self, package_name: &'io PackageName, + index: Option<&'io IndexUrl>, ) -> PackageVersionsResult { let result = self .fetcher .client() - .managed(|client| client.simple(package_name)) + .managed(|client| client.simple(package_name, index, self.capabilities)) .await; match result { @@ -126,7 +131,7 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a, VersionMap::from_metadata( metadata, package_name, - &index, + index, self.tags.as_ref(), &self.requires_python, &self.allowed_yanks, diff --git a/crates/uv-scripts/Cargo.toml b/crates/uv-scripts/Cargo.toml index 1a109dfcb..90d0bef4f 100644 --- a/crates/uv-scripts/Cargo.toml +++ b/crates/uv-scripts/Cargo.toml @@ -4,10 +4,14 @@ version = "0.0.1" edition = "2021" description = "Parse PEP 723-style Python scripts." +[lib] +doctest = false + [lints] workspace = true [dependencies] +uv-distribution-types = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } diff --git a/crates/uv-scripts/src/lib.rs b/crates/uv-scripts/src/lib.rs index c3e0d4b1c..689227475 100644 --- a/crates/uv-scripts/src/lib.rs +++ b/crates/uv-scripts/src/lib.rs @@ -1,13 +1,12 @@ +use memchr::memmem::Finder; +use serde::Deserialize; use std::collections::BTreeMap; use std::io; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::LazyLock; - -use memchr::memmem::Finder; -use serde::Deserialize; use thiserror::Error; - +use uv_distribution_types::Index; use uv_pep440::VersionSpecifiers; use uv_pep508::PackageName; use uv_pypi_types::VerbatimParsedUrl; @@ -16,6 +15,37 @@ use uv_workspace::pyproject::Sources; static FINDER: LazyLock = LazyLock::new(|| Finder::new(b"# /// script")); +/// A PEP 723 item, either read from a script on disk or provided via `stdin`. +#[derive(Debug)] +pub enum Pep723Item { + /// A PEP 723 script read from disk. + Script(Pep723Script), + /// A PEP 723 script provided via `stdin`. + Stdin(Pep723Metadata), + /// A PEP 723 script provided via a remote URL. + Remote(Pep723Metadata), +} + +impl Pep723Item { + /// Return the [`Pep723Metadata`] associated with the item. + pub fn metadata(&self) -> &Pep723Metadata { + match self { + Self::Script(script) => &script.metadata, + Self::Stdin(metadata) => metadata, + Self::Remote(metadata) => metadata, + } + } + + /// Consume the item and return the associated [`Pep723Metadata`]. + pub fn into_metadata(self) -> Pep723Metadata { + match self { + Self::Script(script) => script.metadata, + Self::Stdin(metadata) => metadata, + Self::Remote(metadata) => metadata, + } + } +} + /// A PEP 723 script, including its [`Pep723Metadata`]. #[derive(Debug)] pub struct Pep723Script { @@ -149,7 +179,9 @@ impl Pep723Script { self.postlude ); - Ok(fs_err::tokio::write(&self.path, content).await?) + fs_err::tokio::write(&self.path, content).await?; + + Ok(()) } } @@ -167,6 +199,42 @@ pub struct Pep723Metadata { pub raw: String, } +impl Pep723Metadata { + /// Parse the PEP 723 metadata from `stdin`. + pub fn parse(contents: &[u8]) -> Result, Pep723Error> { + // Extract the `script` tag. + let ScriptTag { metadata, .. } = match ScriptTag::parse(contents) { + Ok(Some(tag)) => tag, + Ok(None) => return Ok(None), + Err(err) => return Err(err), + }; + + // Parse the metadata. + Ok(Some(Self::from_str(&metadata)?)) + } + + /// Read the PEP 723 `script` metadata from a Python file, if it exists. + /// + /// See: + pub async fn read(file: impl AsRef) -> Result, Pep723Error> { + let contents = match fs_err::tokio::read(&file).await { + Ok(contents) => contents, + Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None), + Err(err) => return Err(err.into()), + }; + + // Extract the `script` tag. + let ScriptTag { metadata, .. } = match ScriptTag::parse(&contents) { + Ok(Some(tag)) => tag, + Ok(None) => return Ok(None), + Err(err) => return Err(err), + }; + + // Parse the metadata. + Ok(Some(Self::from_str(&metadata)?)) + } +} + impl FromStr for Pep723Metadata { type Err = Pep723Error; @@ -194,6 +262,7 @@ pub struct ToolUv { #[serde(flatten)] pub top_level: ResolverInstallerOptions, pub sources: Option>, + pub indexes: Option>, } #[derive(Debug, Error)] @@ -412,233 +481,4 @@ fn serialize_metadata(metadata: &str) -> String { } #[cfg(test)] -mod tests { - use crate::{serialize_metadata, Pep723Error, ScriptTag}; - - #[test] - fn missing_space() { - let contents = indoc::indoc! {r" - # /// script - #requires-python = '>=3.11' - # /// - "}; - - assert!(matches!( - ScriptTag::parse(contents.as_bytes()), - Err(Pep723Error::UnclosedBlock) - )); - } - - #[test] - fn no_closing_pragma() { - let contents = indoc::indoc! {r" - # /// script - # requires-python = '>=3.11' - # dependencies = [ - # 'requests<3', - # 'rich', - # ] - "}; - - assert!(matches!( - ScriptTag::parse(contents.as_bytes()), - Err(Pep723Error::UnclosedBlock) - )); - } - - #[test] - fn leading_content() { - let contents = indoc::indoc! {r" - pass # /// script - # requires-python = '>=3.11' - # dependencies = [ - # 'requests<3', - # 'rich', - # ] - # /// - # - # - "}; - - assert_eq!(ScriptTag::parse(contents.as_bytes()).unwrap(), None); - } - - #[test] - fn simple() { - let contents = indoc::indoc! {r" - # /// script - # requires-python = '>=3.11' - # dependencies = [ - # 'requests<3', - # 'rich', - # ] - # /// - - import requests - from rich.pretty import pprint - - resp = requests.get('https://peps.python.org/api/peps.json') - data = resp.json() - "}; - - let expected_metadata = indoc::indoc! {r" - requires-python = '>=3.11' - dependencies = [ - 'requests<3', - 'rich', - ] - "}; - - let expected_data = indoc::indoc! {r" - - import requests - from rich.pretty import pprint - - resp = requests.get('https://peps.python.org/api/peps.json') - data = resp.json() - "}; - - let actual = ScriptTag::parse(contents.as_bytes()).unwrap().unwrap(); - - assert_eq!(actual.prelude, String::new()); - assert_eq!(actual.metadata, expected_metadata); - assert_eq!(actual.postlude, expected_data); - } - - #[test] - fn simple_with_shebang() { - let contents = indoc::indoc! {r" - #!/usr/bin/env python3 - # /// script - # requires-python = '>=3.11' - # dependencies = [ - # 'requests<3', - # 'rich', - # ] - # /// - - import requests - from rich.pretty import pprint - - resp = requests.get('https://peps.python.org/api/peps.json') - data = resp.json() - "}; - - let expected_metadata = indoc::indoc! {r" - requires-python = '>=3.11' - dependencies = [ - 'requests<3', - 'rich', - ] - "}; - - let expected_data = indoc::indoc! {r" - - import requests - from rich.pretty import pprint - - resp = requests.get('https://peps.python.org/api/peps.json') - data = resp.json() - "}; - - let actual = ScriptTag::parse(contents.as_bytes()).unwrap().unwrap(); - - assert_eq!(actual.prelude, "#!/usr/bin/env python3\n".to_string()); - assert_eq!(actual.metadata, expected_metadata); - assert_eq!(actual.postlude, expected_data); - } - #[test] - fn embedded_comment() { - let contents = indoc::indoc! {r" - # /// script - # embedded-csharp = ''' - # /// - # /// text - # /// - # /// - # public class MyClass { } - # ''' - # /// - "}; - - let expected = indoc::indoc! {r" - embedded-csharp = ''' - /// - /// text - /// - /// - public class MyClass { } - ''' - "}; - - let actual = ScriptTag::parse(contents.as_bytes()) - .unwrap() - .unwrap() - .metadata; - - assert_eq!(actual, expected); - } - - #[test] - fn trailing_lines() { - let contents = indoc::indoc! {r" - # /// script - # requires-python = '>=3.11' - # dependencies = [ - # 'requests<3', - # 'rich', - # ] - # /// - # - # - "}; - - let expected = indoc::indoc! {r" - requires-python = '>=3.11' - dependencies = [ - 'requests<3', - 'rich', - ] - "}; - - let actual = ScriptTag::parse(contents.as_bytes()) - .unwrap() - .unwrap() - .metadata; - - assert_eq!(actual, expected); - } - - #[test] - fn test_serialize_metadata_formatting() { - let metadata = indoc::indoc! {r" - requires-python = '>=3.11' - dependencies = [ - 'requests<3', - 'rich', - ] - "}; - - let expected_output = indoc::indoc! {r" - # /// script - # requires-python = '>=3.11' - # dependencies = [ - # 'requests<3', - # 'rich', - # ] - # /// - "}; - - let result = serialize_metadata(metadata); - assert_eq!(result, expected_output); - } - - #[test] - fn test_serialize_metadata_empty() { - let metadata = ""; - let expected_output = "# /// script\n# ///\n"; - - let result = serialize_metadata(metadata); - assert_eq!(result, expected_output); - } -} +mod tests; diff --git a/crates/uv-scripts/src/tests.rs b/crates/uv-scripts/src/tests.rs new file mode 100644 index 000000000..caf508792 --- /dev/null +++ b/crates/uv-scripts/src/tests.rs @@ -0,0 +1,228 @@ +use crate::{serialize_metadata, Pep723Error, ScriptTag}; + +#[test] +fn missing_space() { + let contents = indoc::indoc! {r" + # /// script + #requires-python = '>=3.11' + # /// + "}; + + assert!(matches!( + ScriptTag::parse(contents.as_bytes()), + Err(Pep723Error::UnclosedBlock) + )); +} + +#[test] +fn no_closing_pragma() { + let contents = indoc::indoc! {r" + # /// script + # requires-python = '>=3.11' + # dependencies = [ + # 'requests<3', + # 'rich', + # ] + "}; + + assert!(matches!( + ScriptTag::parse(contents.as_bytes()), + Err(Pep723Error::UnclosedBlock) + )); +} + +#[test] +fn leading_content() { + let contents = indoc::indoc! {r" + pass # /// script + # requires-python = '>=3.11' + # dependencies = [ + # 'requests<3', + # 'rich', + # ] + # /// + # + # + "}; + + assert_eq!(ScriptTag::parse(contents.as_bytes()).unwrap(), None); +} + +#[test] +fn simple() { + let contents = indoc::indoc! {r" + # /// script + # requires-python = '>=3.11' + # dependencies = [ + # 'requests<3', + # 'rich', + # ] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get('https://peps.python.org/api/peps.json') + data = resp.json() + "}; + + let expected_metadata = indoc::indoc! {r" + requires-python = '>=3.11' + dependencies = [ + 'requests<3', + 'rich', + ] + "}; + + let expected_data = indoc::indoc! {r" + + import requests + from rich.pretty import pprint + + resp = requests.get('https://peps.python.org/api/peps.json') + data = resp.json() + "}; + + let actual = ScriptTag::parse(contents.as_bytes()).unwrap().unwrap(); + + assert_eq!(actual.prelude, String::new()); + assert_eq!(actual.metadata, expected_metadata); + assert_eq!(actual.postlude, expected_data); +} + +#[test] +fn simple_with_shebang() { + let contents = indoc::indoc! {r" + #!/usr/bin/env python3 + # /// script + # requires-python = '>=3.11' + # dependencies = [ + # 'requests<3', + # 'rich', + # ] + # /// + + import requests + from rich.pretty import pprint + + resp = requests.get('https://peps.python.org/api/peps.json') + data = resp.json() + "}; + + let expected_metadata = indoc::indoc! {r" + requires-python = '>=3.11' + dependencies = [ + 'requests<3', + 'rich', + ] + "}; + + let expected_data = indoc::indoc! {r" + + import requests + from rich.pretty import pprint + + resp = requests.get('https://peps.python.org/api/peps.json') + data = resp.json() + "}; + + let actual = ScriptTag::parse(contents.as_bytes()).unwrap().unwrap(); + + assert_eq!(actual.prelude, "#!/usr/bin/env python3\n".to_string()); + assert_eq!(actual.metadata, expected_metadata); + assert_eq!(actual.postlude, expected_data); +} +#[test] +fn embedded_comment() { + let contents = indoc::indoc! {r" + # /// script + # embedded-csharp = ''' + # /// + # /// text + # /// + # /// + # public class MyClass { } + # ''' + # /// + "}; + + let expected = indoc::indoc! {r" + embedded-csharp = ''' + /// + /// text + /// + /// + public class MyClass { } + ''' + "}; + + let actual = ScriptTag::parse(contents.as_bytes()) + .unwrap() + .unwrap() + .metadata; + + assert_eq!(actual, expected); +} + +#[test] +fn trailing_lines() { + let contents = indoc::indoc! {r" + # /// script + # requires-python = '>=3.11' + # dependencies = [ + # 'requests<3', + # 'rich', + # ] + # /// + # + # + "}; + + let expected = indoc::indoc! {r" + requires-python = '>=3.11' + dependencies = [ + 'requests<3', + 'rich', + ] + "}; + + let actual = ScriptTag::parse(contents.as_bytes()) + .unwrap() + .unwrap() + .metadata; + + assert_eq!(actual, expected); +} + +#[test] +fn test_serialize_metadata_formatting() { + let metadata = indoc::indoc! {r" + requires-python = '>=3.11' + dependencies = [ + 'requests<3', + 'rich', + ] + "}; + + let expected_output = indoc::indoc! {r" + # /// script + # requires-python = '>=3.11' + # dependencies = [ + # 'requests<3', + # 'rich', + # ] + # /// + "}; + + let result = serialize_metadata(metadata); + assert_eq!(result, expected_output); +} + +#[test] +fn test_serialize_metadata_empty() { + let metadata = ""; + let expected_output = "# /// script\n# ///\n"; + + let result = serialize_metadata(metadata); + assert_eq!(result, expected_output); +} diff --git a/crates/uv-settings/Cargo.toml b/crates/uv-settings/Cargo.toml index 220fd0082..683b9aae3 100644 --- a/crates/uv-settings/Cargo.toml +++ b/crates/uv-settings/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -25,6 +28,7 @@ uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } uv-python = { workspace = true, features = ["schemars", "clap"] } uv-resolver = { workspace = true, features = ["schemars", "clap"] } +uv-static = { workspace = true } uv-warnings = { workspace = true } clap = { workspace = true } diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index f57675695..dfd39085e 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -5,7 +5,7 @@ use url::Url; use uv_configuration::{ ConfigSettings, IndexStrategy, KeyringProviderType, TargetTriple, TrustedPublishing, }; -use uv_distribution_types::IndexUrl; +use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex}; use uv_install_wheel::linker::LinkMode; use uv_pypi_types::SupportedEnvironments; use uv_python::{PythonDownloads, PythonPreference, PythonVersion}; @@ -72,13 +72,16 @@ macro_rules! impl_combine_or { impl_combine_or!(AnnotationStyle); impl_combine_or!(ExcludeNewer); +impl_combine_or!(Index); impl_combine_or!(IndexStrategy); impl_combine_or!(IndexUrl); -impl_combine_or!(Url); impl_combine_or!(KeyringProviderType); impl_combine_or!(LinkMode); impl_combine_or!(NonZeroUsize); impl_combine_or!(PathBuf); +impl_combine_or!(PipExtraIndex); +impl_combine_or!(PipFindLinks); +impl_combine_or!(PipIndex); impl_combine_or!(PrereleaseMode); impl_combine_or!(PythonDownloads); impl_combine_or!(PythonPreference); @@ -88,6 +91,7 @@ impl_combine_or!(String); impl_combine_or!(SupportedEnvironments); impl_combine_or!(TargetTriple); impl_combine_or!(TrustedPublishing); +impl_combine_or!(Url); impl_combine_or!(bool); impl Combine for Option> { diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 9300b2b65..2ee366bf9 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -4,6 +4,8 @@ use std::path::{Path, PathBuf}; use tracing::debug; use uv_fs::Simplified; +#[cfg(not(windows))] +use uv_static::EnvVars; use uv_warnings::warn_user; pub use crate::combine::*; @@ -179,7 +181,7 @@ fn config_dir() -> Option { // On Linux and macOS, use, e.g., /home/alice/.config. #[cfg(not(windows))] { - std::env::var_os("XDG_CONFIG_HOME") + std::env::var_os(EnvVars::XDG_CONFIG_HOME) .and_then(dirs_sys::is_absolute_path) .or_else(|| dirs_sys::home_dir().map(|path| path.join(".config"))) } diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 7ce90d10a..ec880f390 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -1,13 +1,12 @@ -use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf}; - use serde::{Deserialize, Serialize}; +use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf}; use url::Url; use uv_cache_info::CacheKey; use uv_configuration::{ ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple, TrustedHost, TrustedPublishing, }; -use uv_distribution_types::{FlatIndexLocation, IndexUrl, StaticMetadata}; +use uv_distribution_types::{Index, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata}; use uv_install_wheel::linker::LinkMode; use uv_macros::{CombineOptions, OptionsMetadata}; use uv_normalize::{ExtraName, PackageName}; @@ -70,9 +69,9 @@ pub struct Options { /// determine whether any files have changed. /// /// Cache keys can also include version control information. For example, if a project uses - /// `setuptools_scm` to read its version from a Git tag, you can specify `cache-keys = [{ git = true }, { file = "pyproject.toml" }]` + /// `setuptools_scm` to read its version from a Git commit, you can specify `cache-keys = [{ git = { commit = true }, { file = "pyproject.toml" }]` /// to include the current Git commit hash in the cache key (in addition to the - /// `pyproject.toml`). + /// `pyproject.toml`). Git tags are also supported via `cache-keys = [{ git = { commit = true, tags = true } }]`. /// /// Cache keys only affect the project defined by the `pyproject.toml` in which they're /// specified (as opposed to, e.g., affecting all members in a workspace), and all paths and @@ -81,7 +80,7 @@ pub struct Options { default = r#"[{ file = "pyproject.toml" }, { file = "setup.py" }, { file = "setup.cfg" }]"#, value_type = "list[dict]", example = r#" - cache-keys = [{ file = "pyproject.toml" }, { file = "requirements.txt" }, { git = true }] + cache-keys = [{ file = "pyproject.toml" }, { file = "requirements.txt" }, { git = { commit = true }] "# )] cache_keys: Option>, @@ -230,10 +229,11 @@ pub struct GlobalOptions { /// Settings relevant to all installer operations. #[derive(Debug, Clone, Default, CombineOptions)] pub struct InstallerOptions { - pub index_url: Option, - pub extra_index_url: Option>, + pub index: Option>, + pub index_url: Option, + pub extra_index_url: Option>, pub no_index: Option, - pub find_links: Option>, + pub find_links: Option>, pub index_strategy: Option, pub keyring_provider: Option, pub allow_insecure_host: Option>, @@ -254,10 +254,11 @@ pub struct InstallerOptions { /// Settings relevant to all resolver operations. #[derive(Debug, Clone, Default, CombineOptions)] pub struct ResolverOptions { - pub index_url: Option, - pub extra_index_url: Option>, + pub index: Option>, + pub index_url: Option, + pub extra_index_url: Option>, pub no_index: Option, - pub find_links: Option>, + pub find_links: Option>, pub index_strategy: Option, pub keyring_provider: Option, pub allow_insecure_host: Option>, @@ -284,13 +285,52 @@ pub struct ResolverOptions { #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ResolverInstallerOptions { + /// The package indexes to use when resolving dependencies. + /// + /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) + /// (the simple repository API), or a local directory laid out in the same format. + /// + /// Indexes are considered in the order in which they're defined, such that the first-defined + /// index has the highest priority. Further, the indexes provided by this setting are given + /// higher priority than any indexes specified via [`index_url`](#index-url) or + /// [`extra_index_url`](#extra-index-url). uv will only consider the first index that contains + /// a given package, unless an alternative [index strategy](#index-strategy) is specified. + /// + /// If an index is marked as `explicit = true`, it will be used exclusively for those + /// dependencies that select it explicitly via `[tool.uv.sources]`, as in: + /// + /// ```toml + /// [[tool.uv.index]] + /// name = "pytorch" + /// url = "https://download.pytorch.org/whl/cu121" + /// explicit = true + /// + /// [tool.uv.sources] + /// torch = { index = "pytorch" } + /// ``` + /// + /// If an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is + /// given the lowest priority when resolving packages. Additionally, marking an index as default will disable the + /// PyPI default index. + #[option( + default = "\"[]\"", + value_type = "dict", + example = r#" + [[tool.uv.index]] + name = "pytorch" + url = "https://download.pytorch.org/whl/cu121" + "# + )] + pub index: Option>, /// The URL of the Python package index (by default: ). /// /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) /// (the simple repository API), or a local directory laid out in the same format. /// /// The index provided by this setting is given lower priority than any indexes specified via - /// [`extra_index_url`](#extra-index-url). + /// [`extra_index_url`](#extra-index-url) or [`index`](#index). + /// + /// (Deprecated: use `index` instead.) #[option( default = "\"https://pypi.org/simple\"", value_type = "str", @@ -298,17 +338,20 @@ pub struct ResolverInstallerOptions { index-url = "https://test.pypi.org/simple" "# )] - pub index_url: Option, + pub index_url: Option, /// Extra URLs of package indexes to use, in addition to `--index-url`. /// /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) /// (the simple repository API), or a local directory laid out in the same format. /// /// All indexes provided via this flag take priority over the index specified by - /// [`index_url`](#index-url). When multiple indexes are provided, earlier values take priority. + /// [`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes + /// are provided, earlier values take priority. /// /// To control uv's resolution strategy when multiple indexes are present, see /// [`index_strategy`](#index-strategy). + /// + /// (Deprecated: use `index` instead.) #[option( default = "[]", value_type = "list[str]", @@ -316,7 +359,7 @@ pub struct ResolverInstallerOptions { extra-index-url = ["https://download.pytorch.org/whl/cpu"] "# )] - pub extra_index_url: Option>, + pub extra_index_url: Option>, /// Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and /// those provided via `--find-links`. #[option( @@ -342,13 +385,13 @@ pub struct ResolverInstallerOptions { find-links = ["https://download.pytorch.org/whl/torch_stable.html"] "# )] - pub find_links: Option>, + pub find_links: Option>, /// The strategy to use when resolving against multiple index URLs. /// /// By default, uv will stop at the first index on which a given package is available, and /// limit resolutions to those present on that first index (`first-match`). This prevents - /// "dependency confusion" attacks, whereby an attack can upload a malicious package under the - /// same name to a secondary. + /// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the + /// same name to an alternate index. #[option( default = "\"first-index\"", value_type = "str", @@ -693,6 +736,9 @@ pub struct PipOptions { "# )] pub prefix: Option, + #[serde(skip)] + #[cfg_attr(feature = "schemars", schemars(skip))] + pub index: Option>, /// The URL of the Python package index (by default: ). /// /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) @@ -707,7 +753,7 @@ pub struct PipOptions { index-url = "https://test.pypi.org/simple" "# )] - pub index_url: Option, + pub index_url: Option, /// Extra URLs of package indexes to use, in addition to `--index-url`. /// /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) @@ -725,7 +771,7 @@ pub struct PipOptions { extra-index-url = ["https://download.pytorch.org/whl/cpu"] "# )] - pub extra_index_url: Option>, + pub extra_index_url: Option>, /// Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and /// those provided via `--find-links`. #[option( @@ -751,13 +797,13 @@ pub struct PipOptions { find-links = ["https://download.pytorch.org/whl/torch_stable.html"] "# )] - pub find_links: Option>, + pub find_links: Option>, /// The strategy to use when resolving against multiple index URLs. /// /// By default, uv will stop at the first index on which a given package is available, and /// limit resolutions to those present on that first index (`first-match`). This prevents - /// "dependency confusion" attacks, whereby an attack can upload a malicious package under the - /// same name to a secondary. + /// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the + /// same name to an alternate index. #[option( default = "\"first-index\"", value_type = "str", @@ -1299,6 +1345,7 @@ pub struct PipOptions { impl From for ResolverOptions { fn from(value: ResolverInstallerOptions) -> Self { Self { + index: value.index, index_url: value.index_url, extra_index_url: value.extra_index_url, no_index: value.no_index, @@ -1328,6 +1375,7 @@ impl From for ResolverOptions { impl From for InstallerOptions { fn from(value: ResolverInstallerOptions) -> Self { Self { + index: value.index, index_url: value.index_url, extra_index_url: value.extra_index_url, no_index: value.no_index, @@ -1361,10 +1409,11 @@ impl From for InstallerOptions { #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ToolOptions { - pub index_url: Option, - pub extra_index_url: Option>, + pub index: Option>, + pub index_url: Option, + pub extra_index_url: Option>, pub no_index: Option, - pub find_links: Option>, + pub find_links: Option>, pub index_strategy: Option, pub keyring_provider: Option, pub allow_insecure_host: Option>, @@ -1387,6 +1436,7 @@ pub struct ToolOptions { impl From for ToolOptions { fn from(value: ResolverInstallerOptions) -> Self { Self { + index: value.index, index_url: value.index_url, extra_index_url: value.extra_index_url, no_index: value.no_index, @@ -1415,6 +1465,7 @@ impl From for ToolOptions { impl From for ResolverInstallerOptions { fn from(value: ToolOptions) -> Self { Self { + index: value.index, index_url: value.index_url, extra_index_url: value.extra_index_url, no_index: value.no_index, @@ -1450,7 +1501,7 @@ impl From for ResolverInstallerOptions { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct OptionsWire { // #[serde(flatten)] - // globals: GlobalOptions, + // globals: GlobalOptions native_tls: Option, offline: Option, no_cache: Option, @@ -1463,11 +1514,12 @@ pub struct OptionsWire { concurrent_installs: Option, // #[serde(flatten)] - // top_level: ResolverInstallerOptions, - index_url: Option, - extra_index_url: Option>, + // top_level: ResolverInstallerOptions + index: Option>, + index_url: Option, + extra_index_url: Option>, no_index: Option, - find_links: Option>, + find_links: Option>, index_strategy: Option, keyring_provider: Option, allow_insecure_host: Option>, @@ -1489,6 +1541,9 @@ pub struct OptionsWire { no_build_package: Option>, no_binary: Option, no_binary_package: Option>, + + // #[serde(flatten)] + // publish: PublishOptions publish_url: Option, trusted_publishing: Option, @@ -1528,6 +1583,7 @@ impl From for Options { concurrent_downloads, concurrent_builds, concurrent_installs, + index, index_url, extra_index_url, no_index, @@ -1581,6 +1637,7 @@ impl From for Options { concurrent_installs, }, top_level: ResolverInstallerOptions { + index, index_url, extra_index_url, no_index, diff --git a/crates/uv-shell/Cargo.toml b/crates/uv-shell/Cargo.toml index a6040c07a..f98be0990 100644 --- a/crates/uv-shell/Cargo.toml +++ b/crates/uv-shell/Cargo.toml @@ -4,11 +4,15 @@ version = "0.0.1" edition = "2021" description = "Utilities for detecting and manipulating shell environments" +[lib] +doctest = false + [lints] workspace = true [dependencies] uv-fs = { workspace = true } +uv-static = { workspace = true } anyhow = { workspace = true } home = { workspace = true } diff --git a/crates/uv-shell/src/lib.rs b/crates/uv-shell/src/lib.rs index 75cab2779..5ba401dbe 100644 --- a/crates/uv-shell/src/lib.rs +++ b/crates/uv-shell/src/lib.rs @@ -2,6 +2,7 @@ pub mod windows; use std::path::{Path, PathBuf}; use uv_fs::Simplified; +use uv_static::EnvVars; /// Shells for which virtualenv activation scripts are available. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -37,22 +38,22 @@ impl Shell { /// If `SHELL` is set, but contains a value that doesn't correspond to one of the supported /// shell types, then return `None`. pub fn from_env() -> Option { - if std::env::var_os("NU_VERSION").is_some() { + if std::env::var_os(EnvVars::NU_VERSION).is_some() { Some(Shell::Nushell) - } else if std::env::var_os("FISH_VERSION").is_some() { + } else if std::env::var_os(EnvVars::FISH_VERSION).is_some() { Some(Shell::Fish) - } else if std::env::var_os("BASH_VERSION").is_some() { + } else if std::env::var_os(EnvVars::BASH_VERSION).is_some() { Some(Shell::Bash) - } else if std::env::var_os("ZSH_VERSION").is_some() { + } else if std::env::var_os(EnvVars::ZSH_VERSION).is_some() { Some(Shell::Zsh) - } else if std::env::var_os("KSH_VERSION").is_some() { + } else if std::env::var_os(EnvVars::KSH_VERSION).is_some() { Some(Shell::Ksh) - } else if let Some(env_shell) = std::env::var_os("SHELL") { + } else if let Some(env_shell) = std::env::var_os(EnvVars::SHELL) { Shell::from_shell_path(env_shell) } else if cfg!(windows) { // Command Prompt relies on PROMPT for its appearance whereas PowerShell does not. // See: https://stackoverflow.com/a/66415037. - if std::env::var_os("PROMPT").is_some() { + if std::env::var_os(EnvVars::PROMPT).is_some() { Some(Shell::Cmd) } else { // Fallback to PowerShell if the PROMPT environment variable is not set. @@ -113,7 +114,7 @@ impl Shell { // `.zshenv` to use. // // See: https://github.com/rust-lang/rustup/blob/fede22fea7b160868cece632bd213e6d72f8912f/src/cli/self_update/shell.rs#L197 - let zsh_dot_dir = std::env::var("ZDOTDIR") + let zsh_dot_dir = std::env::var(EnvVars::ZDOTDIR) .ok() .filter(|dir| !dir.is_empty()) .map(PathBuf::from); @@ -146,7 +147,7 @@ impl Shell { // login and non-login shells. However, we must respect Fish's logic, which reads // from `$XDG_CONFIG_HOME/fish/config.fish` if set, and `~/.config/fish/config.fish` // otherwise. - if let Some(xdg_home_dir) = std::env::var("XDG_CONFIG_HOME") + if let Some(xdg_home_dir) = std::env::var(EnvVars::XDG_CONFIG_HOME) .ok() .filter(|dir| !dir.is_empty()) .map(PathBuf::from) @@ -172,7 +173,7 @@ impl Shell { /// Returns `true` if the given path is on the `PATH` in this shell. pub fn contains_path(path: &Path) -> bool { let home_dir = home::home_dir(); - std::env::var_os("PATH") + std::env::var_os(EnvVars::PATH) .as_ref() .iter() .flat_map(std::env::split_paths) diff --git a/crates/uv-shell/src/windows.rs b/crates/uv-shell/src/windows.rs index 8e6e6b124..68cf9aa8c 100644 --- a/crates/uv-shell/src/windows.rs +++ b/crates/uv-shell/src/windows.rs @@ -11,6 +11,7 @@ use std::path::Path; use std::slice; use anyhow::Context; +use uv_static::EnvVars; use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE}; use winreg::{RegKey, RegValue}; @@ -44,13 +45,13 @@ fn apply_windows_path_var(path: Vec) -> anyhow::Result<()> { let environment = root.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)?; if path.is_empty() { - environment.delete_value("PATH")?; + environment.delete_value(EnvVars::PATH)?; } else { let reg_value = RegValue { bytes: to_winreg_bytes(path), vtype: RegType::REG_EXPAND_SZ, }; - environment.set_raw_value("PATH", ®_value)?; + environment.set_raw_value(EnvVars::PATH, ®_value)?; } Ok(()) @@ -65,7 +66,7 @@ fn get_windows_path_var() -> anyhow::Result>> { .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) .context("Failed to open `Environment` key")?; - let reg_value = environment.get_raw_value("PATH"); + let reg_value = environment.get_raw_value(EnvVars::PATH); match reg_value { Ok(reg_value) => { if let Some(reg_value) = from_winreg_value(®_value) { diff --git a/crates/uv-state/Cargo.toml b/crates/uv-state/Cargo.toml index 3b3bd2c1f..f90413be4 100644 --- a/crates/uv-state/Cargo.toml +++ b/crates/uv-state/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-static/Cargo.toml b/crates/uv-static/Cargo.toml new file mode 100644 index 000000000..b054021e6 --- /dev/null +++ b/crates/uv-static/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "uv-static" +version = "0.0.1" +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +license = { workspace = true } + +[lib] +doctest = false + +[lints] +workspace = true + +[dependencies] diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs new file mode 100644 index 000000000..64fa737c0 --- /dev/null +++ b/crates/uv-static/src/env_vars.rs @@ -0,0 +1,380 @@ +/// Declares all environment variable used throughout `uv` and its crates. +pub struct EnvVars; + +impl EnvVars { + /// Equivalent to the `--default-index` argument. Base index URL for searching packages. + pub const UV_DEFAULT_INDEX: &'static str = "UV_DEFAULT_INDEX"; + + /// Equivalent to the `--index` argument. Additional indexes for searching packages. + pub const UV_INDEX: &'static str = "UV_INDEX"; + + /// Equivalent to the `--index-url` argument. Base index URL for searching packages. + /// + /// Deprecated: use `UV_DEFAULT_INDEX` instead. + pub const UV_INDEX_URL: &'static str = "UV_INDEX_URL"; + + /// Equivalent to the `--extra-index-url` argument. Additional indexes for searching packages. + /// + /// Deprecated: use `UV_INDEX` instead. + pub const UV_EXTRA_INDEX_URL: &'static str = "UV_EXTRA_INDEX_URL"; + + /// Equivalent to the `--find-links` argument. Additional package search locations. + pub const UV_FIND_LINKS: &'static str = "UV_FIND_LINKS"; + + /// Equivalent to the `--cache-dir` argument. Custom directory for caching. + pub const UV_CACHE_DIR: &'static str = "UV_CACHE_DIR"; + + /// Equivalent to the `--no-cache` argument. Disables cache usage. + pub const UV_NO_CACHE: &'static str = "UV_NO_CACHE"; + + /// Equivalent to the `--resolution` argument. Controls dependency resolution strategy. + pub const UV_RESOLUTION: &'static str = "UV_RESOLUTION"; + + /// Equivalent to the `--prerelease` argument. Allows or disallows pre-release versions. + pub const UV_PRERELEASE: &'static str = "UV_PRERELEASE"; + + /// Equivalent to the `--system` argument. Use system Python interpreter. + pub const UV_SYSTEM_PYTHON: &'static str = "UV_SYSTEM_PYTHON"; + + /// Equivalent to the `--python` argument. Path to a specific Python interpreter. + pub const UV_PYTHON: &'static str = "UV_PYTHON"; + + /// Equivalent to the `--break-system-packages` argument. Allows breaking system packages. + pub const UV_BREAK_SYSTEM_PACKAGES: &'static str = "UV_BREAK_SYSTEM_PACKAGES"; + + /// Equivalent to the `--native-tls` argument. Uses system's trust store for TLS. + pub const UV_NATIVE_TLS: &'static str = "UV_NATIVE_TLS"; + + /// Equivalent to the `--index-strategy` argument. Defines strategy for searching index URLs. + pub const UV_INDEX_STRATEGY: &'static str = "UV_INDEX_STRATEGY"; + + /// Equivalent to the `--require-hashes` argument. Requires hashes for all dependencies. + pub const UV_REQUIRE_HASHES: &'static str = "UV_REQUIRE_HASHES"; + + /// Equivalent to the `--constraint` argument. Path to constraints file. + pub const UV_CONSTRAINT: &'static str = "UV_CONSTRAINT"; + + /// Equivalent to the `--build-constraint` argument. Path to build constraints file. + pub const UV_BUILD_CONSTRAINT: &'static str = "UV_BUILD_CONSTRAINT"; + + /// Equivalent to the `--override` argument. Path to overrides file. + pub const UV_OVERRIDE: &'static str = "UV_OVERRIDE"; + + /// Equivalent to the `--link-mode` argument. Specifies link mode for the installation. + pub const UV_LINK_MODE: &'static str = "UV_LINK_MODE"; + + /// Equivalent to the `--no-build-isolation` argument. Skips build isolation. + pub const UV_NO_BUILD_ISOLATION: &'static str = "UV_NO_BUILD_ISOLATION"; + + /// Equivalent to the `--custom-compile-command` argument. Overrides the command in `requirements.txt`. + pub const UV_CUSTOM_COMPILE_COMMAND: &'static str = "UV_CUSTOM_COMPILE_COMMAND"; + + /// Equivalent to the `--keyring-provider` argument. Specifies keyring provider. + pub const UV_KEYRING_PROVIDER: &'static str = "UV_KEYRING_PROVIDER"; + + /// Equivalent to the `--config-file` argument. Path to configuration file. + pub const UV_CONFIG_FILE: &'static str = "UV_CONFIG_FILE"; + + /// Equivalent to the `--no-config` argument. Prevents reading configuration files. + pub const UV_NO_CONFIG: &'static str = "UV_NO_CONFIG"; + + /// Equivalent to the `--exclude-newer` argument. Excludes newer distributions after a date. + pub const UV_EXCLUDE_NEWER: &'static str = "UV_EXCLUDE_NEWER"; + + /// Equivalent to the `--python-preference` argument. Controls preference for Python versions. + pub const UV_PYTHON_PREFERENCE: &'static str = "UV_PYTHON_PREFERENCE"; + + /// Equivalent to the `--no-python-downloads` argument. Disables Python downloads. + pub const UV_PYTHON_DOWNLOADS: &'static str = "UV_PYTHON_DOWNLOADS"; + + /// Equivalent to the `--compile-bytecode` argument. Compiles Python source to bytecode. + pub const UV_COMPILE_BYTECODE: &'static str = "UV_COMPILE_BYTECODE"; + + /// Equivalent to the `--publish-url` argument. URL for publishing packages. + pub const UV_PUBLISH_URL: &'static str = "UV_PUBLISH_URL"; + + /// Equivalent to the `--token` argument in `uv publish`. Token for publishing. + pub const UV_PUBLISH_TOKEN: &'static str = "UV_PUBLISH_TOKEN"; + + /// Equivalent to the `--username` argument in `uv publish`. Username for publishing. + pub const UV_PUBLISH_USERNAME: &'static str = "UV_PUBLISH_USERNAME"; + + /// Equivalent to the `--password` argument in `uv publish`. Password for publishing. + pub const UV_PUBLISH_PASSWORD: &'static str = "UV_PUBLISH_PASSWORD"; + + /// Equivalent to the `--no-sync` argument. Skips syncing the environment. + pub const UV_NO_SYNC: &'static str = "UV_NO_SYNC"; + + /// Equivalent to the `--preview` argument. Enables preview mode. + pub const UV_PREVIEW: &'static str = "UV_PREVIEW"; + + /// Equivalent to the `--token` argument for self update. A GitHub token for authentication. + pub const UV_GITHUB_TOKEN: &'static str = "UV_GITHUB_TOKEN"; + + /// Equivalent to the `--verify-hashes` argument. Verifies included hashes. + pub const UV_VERIFY_HASHES: &'static str = "UV_VERIFY_HASHES"; + + /// Equivalent to the `--allow-insecure-host` argument. + pub const UV_INSECURE_HOST: &'static str = "UV_INSECURE_HOST"; + + /// Sets the maximum number of in-flight concurrent downloads. + pub const UV_CONCURRENT_DOWNLOADS: &'static str = "UV_CONCURRENT_DOWNLOADS"; + + /// Sets the maximum number of concurrent builds for source distributions. + pub const UV_CONCURRENT_BUILDS: &'static str = "UV_CONCURRENT_BUILDS"; + + /// Controls the number of threads used for concurrent installations. + pub const UV_CONCURRENT_INSTALLS: &'static str = "UV_CONCURRENT_INSTALLS"; + + /// Specifies the directory where `uv` stores managed tools. + pub const UV_TOOL_DIR: &'static str = "UV_TOOL_DIR"; + + /// Specifies the "bin" directory for installing tool executables. + pub const UV_TOOL_BIN_DIR: &'static str = "UV_TOOL_BIN_DIR"; + + /// Specifies the path to the project virtual environment. + pub const UV_PROJECT_ENVIRONMENT: &'static str = "UV_PROJECT_ENVIRONMENT"; + + /// Specifies the directory for storing managed Python installations. + pub const UV_PYTHON_INSTALL_DIR: &'static str = "UV_PYTHON_INSTALL_DIR"; + + /// Mirror URL for downloading managed Python installations. + pub const UV_PYTHON_INSTALL_MIRROR: &'static str = "UV_PYTHON_INSTALL_MIRROR"; + + /// Mirror URL for downloading managed PyPy installations. + pub const UV_PYPY_INSTALL_MIRROR: &'static str = "UV_PYPY_INSTALL_MIRROR"; + + /// Used to override `PATH` to limit Python executable availability in the test suite. + pub const UV_TEST_PYTHON_PATH: &'static str = "UV_TEST_PYTHON_PATH"; + + /// Include resolver and installer output related to environment modifications. + pub const UV_SHOW_RESOLUTION: &'static str = "UV_SHOW_RESOLUTION"; + + /// Use to update the json schema files. + pub const UV_UPDATE_SCHEMA: &'static str = "UV_UPDATE_SCHEMA"; + + /// Use to disable line wrapping for diagnostics. + pub const UV_NO_WRAP: &'static str = "UV_NO_WRAP"; + + /// Use to control the stack size used by uv. Typically more relevant for Windows in debug mode. + pub const UV_STACK_SIZE: &'static str = "UV_STACK_SIZE"; + + /// Generates the environment variable key for the HTTP Basic authentication username. + pub fn http_basic_username(name: &str) -> String { + format!("UV_HTTP_BASIC_{name}_USERNAME") + } + + /// Generates the environment variable key for the HTTP Basic authentication password. + pub fn http_basic_password(name: &str) -> String { + format!("UV_HTTP_BASIC_{name}_PASSWORD") + } + + /// Used to set the uv commit hash at build time via `build.rs`. + pub const UV_COMMIT_HASH: &'static str = "UV_COMMIT_HASH"; + + /// Used to set the uv commit short hash at build time via `build.rs`. + pub const UV_COMMIT_SHORT_HASH: &'static str = "UV_COMMIT_SHORT_HASH"; + + /// Used to set the uv commit date at build time via `build.rs`. + pub const UV_COMMIT_DATE: &'static str = "UV_COMMIT_DATE"; + + /// Used to set the uv tag at build time via `build.rs`. + pub const UV_LAST_TAG: &'static str = "UV_LAST_TAG"; + + /// Used to set the uv tag distance from head at build time via `build.rs`. + pub const UV_LAST_TAG_DISTANCE: &'static str = "UV_LAST_TAG_DISTANCE"; + + /// Used in tests for testing providing credentials via environment variables. + pub const UV_HTTP_BASIC_PROXY_USERNAME: &'static str = "UV_HTTP_BASIC_PROXY_USERNAME"; + + /// Used in tests for testing providing credentials via environment variables. + pub const UV_HTTP_BASIC_PROXY_PASSWORD: &'static str = "UV_HTTP_BASIC_PROXY_PASSWORD"; + + /// Used to set the spawning/parent interpreter when using --system in the test suite. + pub const UV_INTERNAL__PARENT_INTERPRETER: &'static str = "UV_INTERNAL__PARENT_INTERPRETER"; + + /// Used to force showing the derivation tree during resolver error reporting. + pub const UV_INTERNAL__SHOW_DERIVATION_TREE: &'static str = "UV_INTERNAL__SHOW_DERIVATION_TREE"; + + /// Used to set a temporary directory for some tests. + pub const UV_INTERNAL__TEST_DIR: &'static str = "UV_INTERNAL__TEST_DIR"; + + /// Path to user-level configuration directory on Unix systems. + pub const XDG_CONFIG_HOME: &'static str = "XDG_CONFIG_HOME"; + + /// Path to cache directory on Unix systems. + pub const XDG_CACHE_HOME: &'static str = "XDG_CACHE_HOME"; + + /// Path to directory for storing managed Python installations and tools. + pub const XDG_DATA_HOME: &'static str = "XDG_DATA_HOME"; + + /// Path to directory where executables are installed. + pub const XDG_BIN_HOME: &'static str = "XDG_BIN_HOME"; + + /// Timeout (in seconds) for HTTP requests. + pub const UV_HTTP_TIMEOUT: &'static str = "UV_HTTP_TIMEOUT"; + + /// Timeout (in seconds) for HTTP requests. + pub const UV_REQUEST_TIMEOUT: &'static str = "UV_REQUEST_TIMEOUT"; + + /// Timeout (in seconds) for HTTP requests. + pub const HTTP_TIMEOUT: &'static str = "HTTP_TIMEOUT"; + + /// Custom certificate bundle file path for SSL connections. + pub const SSL_CERT_FILE: &'static str = "SSL_CERT_FILE"; + + /// File for mTLS authentication (contains certificate and private key). + pub const SSL_CLIENT_CERT: &'static str = "SSL_CLIENT_CERT"; + + /// Proxy for HTTP requests. + pub const HTTP_PROXY: &'static str = "HTTP_PROXY"; + + /// Proxy for HTTPS requests. + pub const HTTPS_PROXY: &'static str = "HTTPS_PROXY"; + + /// General proxy for all network requests. + pub const ALL_PROXY: &'static str = "ALL_PROXY"; + + /// Used to detect an activated virtual environment. + pub const VIRTUAL_ENV: &'static str = "VIRTUAL_ENV"; + + /// Used to detect an activated Conda environment. + pub const CONDA_PREFIX: &'static str = "CONDA_PREFIX"; + + /// Disables prepending virtual environment name to the terminal prompt. + pub const VIRTUAL_ENV_DISABLE_PROMPT: &'static str = "VIRTUAL_ENV_DISABLE_PROMPT"; + + /// Used to detect Windows Command Prompt usage. + pub const PROMPT: &'static str = "PROMPT"; + + /// Used to detect `NuShell` usage. + pub const NU_VERSION: &'static str = "NU_VERSION"; + + /// Used to detect Fish shell usage. + pub const FISH_VERSION: &'static str = "FISH_VERSION"; + + /// Used to detect Bash shell usage. + pub const BASH_VERSION: &'static str = "BASH_VERSION"; + + /// Used to detect Zsh shell usage. + pub const ZSH_VERSION: &'static str = "ZSH_VERSION"; + + /// Used to determine which `.zshenv` to use when Zsh is being used. + pub const ZDOTDIR: &'static str = "ZDOTDIR"; + + /// Used to detect Ksh shell usage. + pub const KSH_VERSION: &'static str = "KSH_VERSION"; + + /// Sets macOS deployment target when using `--python-platform macos`. + pub const MACOSX_DEPLOYMENT_TARGET: &'static str = "MACOSX_DEPLOYMENT_TARGET"; + + /// Disables colored output (takes precedence over `FORCE_COLOR`). + pub const NO_COLOR: &'static str = "NO_COLOR"; + + /// Forces colored output regardless of terminal support. + pub const FORCE_COLOR: &'static str = "FORCE_COLOR"; + + /// Use to control color via `anstyle`. + pub const CLICOLOR_FORCE: &'static str = "CLICOLOR_FORCE"; + + /// The standard `PATH` env var. + pub const PATH: &'static str = "PATH"; + + /// The standard `HOME` env var. + pub const HOME: &'static str = "HOME"; + + /// The standard `SHELL` posix env var. + pub const SHELL: &'static str = "SHELL"; + + /// The standard `PWD` posix env var. + pub const PWD: &'static str = "PWD"; + + /// Used to look for Microsoft Store Pythons installations. + pub const LOCALAPPDATA: &'static str = "LOCALAPPDATA"; + + /// Path to the `.git` directory. Ignored by `uv` when performing fetch. + pub const GIT_DIR: &'static str = "GIT_DIR"; + + /// Path to the git working tree. Ignored by `uv` when performing fetch. + pub const GIT_WORK_TREE: &'static str = "GIT_WORK_TREE"; + + /// Path to the index file for staged changes. Ignored by `uv` when performing fetch. + pub const GIT_INDEX_FILE: &'static str = "GIT_INDEX_FILE"; + + /// Path to where git object files are located. Ignored by `uv` when performing fetch. + pub const GIT_OBJECT_DIRECTORY: &'static str = "GIT_OBJECT_DIRECTORY"; + + /// Alternate locations for git objects. Ignored by `uv` when performing fetch. + pub const GIT_ALTERNATE_OBJECT_DIRECTORIES: &'static str = "GIT_ALTERNATE_OBJECT_DIRECTORIES"; + + /// Used for trusted publishing via `uv publish`. + pub const GITHUB_ACTIONS: &'static str = "GITHUB_ACTIONS"; + + /// Used for trusted publishing via `uv publish`. Contains the oidc token url. + pub const ACTIONS_ID_TOKEN_REQUEST_URL: &'static str = "ACTIONS_ID_TOKEN_REQUEST_URL"; + + /// Used for trusted publishing via `uv publish`. Contains the oidc request token. + pub const ACTIONS_ID_TOKEN_REQUEST_TOKEN: &'static str = "ACTIONS_ID_TOKEN_REQUEST_TOKEN"; + + /// Sets the encoding for standard I/O streams (e.g., PYTHONIOENCODING=utf-8). + pub const PYTHONIOENCODING: &'static str = "PYTHONIOENCODING"; + + /// Forces unbuffered I/O streams, equivalent to `-u` in Python. + pub const PYTHONUNBUFFERED: &'static str = "PYTHONUNBUFFERED"; + + /// Enables UTF-8 mode for Python, equivalent to `-X utf8`. + pub const PYTHONUTF8: &'static str = "PYTHONUTF8"; + + /// Adds directories to Python module search path (e.g., PYTHONPATH=/path/to/modules). + pub const PYTHONPATH: &'static str = "PYTHONPATH"; + + /// Typically set by CI runners, used to detect a CI runner. + pub const CI: &'static str = "CI"; + + /// Use to set the .netrc file location. + pub const NETRC: &'static str = "NETRC"; + + /// The standard `PAGER` posix env var. Used by `uv` to configure the appropriate pager. + pub const PAGER: &'static str = "PAGER"; + + /// Used to detect when running inside a Jupyter notebook. + pub const JPY_SESSION_NAME: &'static str = "JPY_SESSION_NAME"; + + /// Use to create the tracing root directory via the `tracing-durations-export` feature. + pub const TRACING_DURATIONS_TEST_ROOT: &'static str = "TRACING_DURATIONS_TEST_ROOT"; + + /// Use to create the tracing durations file via the `tracing-durations-export` feature. + pub const TRACING_DURATIONS_FILE: &'static str = "TRACING_DURATIONS_FILE"; + + /// Used to set `RUST_HOST_TARGET` at build time via `build.rs`. + pub const TARGET: &'static str = "TARGET"; + + /// Custom log level for verbose output, compatible with `tracing_subscriber`. + pub const RUST_LOG: &'static str = "RUST_LOG"; + + /// The directory containing the `Cargo.toml` manifest for a package. + pub const CARGO_MANIFEST_DIR: &'static str = "CARGO_MANIFEST_DIR"; + + /// Specifies the directory where Cargo stores build artifacts (target directory). + pub const CARGO_TARGET_DIR: &'static str = "CARGO_TARGET_DIR"; + + /// Used in tests for environment substitution testing in `requirements.in`. + pub const URL: &'static str = "URL"; + + /// Used in tests for environment substitution testing in `requirements.in`. + pub const FILE_PATH: &'static str = "FILE_PATH"; + + /// Used in tests for environment substitution testing in `requirements.in`. + pub const HATCH_PATH: &'static str = "HATCH_PATH"; + + /// Used in tests for environment substitution testing in `requirements.in`. + pub const BLACK_PATH: &'static str = "BLACK_PATH"; + + /// Used in testing Hatch's root.uri feature + /// + /// See: . + pub const ROOT_PATH: &'static str = "ROOT_PATH"; + + /// Used to set test credentials for keyring tests. + pub const KEYRING_TEST_CREDENTIALS: &'static str = "KEYRING_TEST_CREDENTIALS"; +} diff --git a/crates/uv-static/src/lib.rs b/crates/uv-static/src/lib.rs new file mode 100644 index 000000000..153591db7 --- /dev/null +++ b/crates/uv-static/src/lib.rs @@ -0,0 +1,3 @@ +pub use env_vars::*; + +mod env_vars; diff --git a/crates/uv-tool/Cargo.toml b/crates/uv-tool/Cargo.toml index 10261c528..f74b29fc2 100644 --- a/crates/uv-tool/Cargo.toml +++ b/crates/uv-tool/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true @@ -23,6 +26,7 @@ uv-pypi-types = { workspace = true } uv-python = { workspace = true } uv-settings = { workspace = true } uv-state = { workspace = true } +uv-static = { workspace = true } uv-virtualenv = { workspace = true } dirs-sys = { workspace = true } diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs index 647ace743..20b3e559f 100644 --- a/crates/uv-tool/src/lib.rs +++ b/crates/uv-tool/src/lib.rs @@ -22,6 +22,7 @@ use uv_fs::{LockedFile, Simplified}; use uv_installer::SitePackages; use uv_python::{Interpreter, PythonEnvironment}; use uv_state::{StateBucket, StateStore}; +use uv_static::EnvVars; mod receipt; mod tool; @@ -77,7 +78,7 @@ impl InstalledTools { /// 2. A directory in the system-appropriate user-level data directory, e.g., `~/.local/uv/tools` /// 3. A directory in the local data directory, e.g., `./.uv/tools` pub fn from_settings() -> Result { - if let Some(tool_dir) = std::env::var_os("UV_TOOL_DIR") { + if let Some(tool_dir) = std::env::var_os(EnvVars::UV_TOOL_DIR) { Ok(Self::from_path(tool_dir)) } else { Ok(Self::from_path( @@ -366,11 +367,11 @@ impl fmt::Display for InstalledTool { /// /// Errors if a directory cannot be found. pub fn find_executable_directory() -> Result { - std::env::var_os("UV_TOOL_BIN_DIR") + std::env::var_os(EnvVars::UV_TOOL_BIN_DIR) .and_then(dirs_sys::is_absolute_path) - .or_else(|| std::env::var_os("XDG_BIN_HOME").and_then(dirs_sys::is_absolute_path)) + .or_else(|| std::env::var_os(EnvVars::XDG_BIN_HOME).and_then(dirs_sys::is_absolute_path)) .or_else(|| { - std::env::var_os("XDG_DATA_HOME") + std::env::var_os(EnvVars::XDG_DATA_HOME) .and_then(dirs_sys::is_absolute_path) .map(|path| path.join("../bin")) }) diff --git a/crates/uv-trampoline/Cargo.toml b/crates/uv-trampoline/Cargo.toml index d6de598d4..92d5fa87e 100644 --- a/crates/uv-trampoline/Cargo.toml +++ b/crates/uv-trampoline/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Nathaniel J. Smith "] license = "MIT OR Apache-2.0" edition = "2021" +[lib] +doctest = false + # Need to optimize etc. or else build fails [profile.dev] lto = true diff --git a/crates/uv-types/Cargo.toml b/crates/uv-types/Cargo.toml index 6b47a31ce..57ac4571a 100644 --- a/crates/uv-types/Cargo.toml +++ b/crates/uv-types/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-types/src/traits.rs b/crates/uv-types/src/traits.rs index 0d0a6db73..72b84d95f 100644 --- a/crates/uv-types/src/traits.rs +++ b/crates/uv-types/src/traits.rs @@ -4,7 +4,9 @@ use std::path::{Path, PathBuf}; use anyhow::Result; use uv_cache::Cache; -use uv_configuration::{BuildKind, BuildOptions, BuildOutput, ConfigSettings, SourceStrategy}; +use uv_configuration::{ + BuildKind, BuildOptions, BuildOutput, ConfigSettings, LowerBound, SourceStrategy, +}; use uv_distribution_types::{ CachedDist, DependencyMetadata, IndexCapabilities, IndexLocations, InstalledDist, Resolution, SourceDist, @@ -75,11 +77,14 @@ pub trait BuildContext { /// The [`ConfigSettings`] used to build distributions. fn config_settings(&self) -> &ConfigSettings; + /// Whether to warn on missing lower bounds. + fn bounds(&self) -> LowerBound; + /// Whether to incorporate `tool.uv.sources` when resolving requirements. fn sources(&self) -> SourceStrategy; /// The index locations being searched. - fn index_locations(&self) -> &IndexLocations; + fn locations(&self) -> &IndexLocations; /// Resolve the given requirements into a ready-to-install set of package versions. fn resolve<'a>( @@ -106,8 +111,10 @@ pub trait BuildContext { &'a self, source: &'a Path, subdirectory: Option<&'a Path>, + install_path: &'a Path, version_id: Option, dist: Option<&'a SourceDist>, + sources: SourceStrategy, build_kind: BuildKind, build_output: BuildOutput, ) -> impl Future> + 'a; diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 33eecd9fc..dbf6d8f9a 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.4.18" +version = "0.4.23" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-version/src/lib.rs b/crates/uv-version/src/lib.rs index 20a8940a1..bf17cf1e8 100644 --- a/crates/uv-version/src/lib.rs +++ b/crates/uv-version/src/lib.rs @@ -6,11 +6,4 @@ pub fn version() -> &'static str { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_get_version() { - assert_eq!(version().to_string(), env!("CARGO_PKG_VERSION").to_string()); - } -} +mod tests; diff --git a/crates/uv-version/src/tests.rs b/crates/uv-version/src/tests.rs new file mode 100644 index 000000000..7887964c5 --- /dev/null +++ b/crates/uv-version/src/tests.rs @@ -0,0 +1,6 @@ +use super::*; + +#[test] +fn test_get_version() { + assert_eq!(version().to_string(), env!("CARGO_PKG_VERSION").to_string()); +} diff --git a/crates/uv-virtualenv/Cargo.toml b/crates/uv-virtualenv/Cargo.toml index fed03e0c9..959a17e20 100644 --- a/crates/uv-virtualenv/Cargo.toml +++ b/crates/uv-virtualenv/Cargo.toml @@ -13,6 +13,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-virtualenv/src/activator/activate b/crates/uv-virtualenv/src/activator/activate index b6a128332..5a49d9e48 100644 --- a/crates/uv-virtualenv/src/activator/activate +++ b/crates/uv-virtualenv/src/activator/activate @@ -85,9 +85,9 @@ PATH="$VIRTUAL_ENV/{{ BIN_NAME }}:$PATH" export PATH if [ "x{{ VIRTUAL_PROMPT }}" != x ] ; then - VIRTUAL_ENV_PROMPT="{{ VIRTUAL_PROMPT }}" + VIRTUAL_ENV_PROMPT="({{ VIRTUAL_PROMPT }}) " else - VIRTUAL_ENV_PROMPT=$(basename "$VIRTUAL_ENV") + VIRTUAL_ENV_PROMPT="($(basename "$VIRTUAL_ENV")) " fi export VIRTUAL_ENV_PROMPT @@ -99,7 +99,7 @@ fi if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then _OLD_VIRTUAL_PS1="${PS1-}" - PS1="(${VIRTUAL_ENV_PROMPT}) ${PS1-}" + PS1="${VIRTUAL_ENV_PROMPT}${PS1-}" export PS1 fi diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index d46839707..70f0ca22e 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -298,9 +298,7 @@ pub(crate) fn create( let virtual_env_dir = match (relocatable, name.to_owned()) { (true, "activate") => { - // Extremely verbose, but should cover all major POSIX shells, - // as well as platforms where `readlink` does not implement `-f`. - r#"'"$(dirname -- "$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" > /dev/null && echo "$PWD")")"'"# + r#"'"$(dirname -- "$(dirname -- "$(realpath -- "$SCRIPT_PATH")")")"'"# } (true, "activate.bat") => r"%~dp0..", (true, "activate.fish") => { diff --git a/crates/uv-warnings/Cargo.toml b/crates/uv-warnings/Cargo.toml index c5b281e2e..74fdd61b8 100644 --- a/crates/uv-warnings/Cargo.toml +++ b/crates/uv-warnings/Cargo.toml @@ -9,6 +9,9 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true diff --git a/crates/uv-workspace/Cargo.toml b/crates/uv-workspace/Cargo.toml index fe6125ce2..040f9fe06 100644 --- a/crates/uv-workspace/Cargo.toml +++ b/crates/uv-workspace/Cargo.toml @@ -9,10 +9,14 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } +[lib] +doctest = false + [lints] workspace = true [dependencies] +uv-distribution-types = { workspace = true } uv-fs = { workspace = true, features = ["tokio", "schemars"] } uv-git = { workspace = true } uv-macros = { workspace = true } @@ -21,6 +25,7 @@ uv-options-metadata = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } +uv-static = { workspace = true } uv-warnings = { workspace = true } either = { workspace = true } diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 9420934cf..c72a3d7d5 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -15,7 +15,7 @@ use std::str::FromStr; use std::{collections::BTreeMap, mem}; use thiserror::Error; use url::Url; - +use uv_distribution_types::Index; use uv_fs::{relative_to, PortablePathBuf}; use uv_git::GitReference; use uv_macros::OptionsMetadata; @@ -154,9 +154,49 @@ pub struct ToolUv { /// The sources to use (e.g., workspace members, Git repositories, local paths) when resolving /// dependencies. pub sources: Option, + + /// The indexes to use when resolving dependencies. + /// + /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) + /// (the simple repository API), or a local directory laid out in the same format. + /// + /// Indexes are considered in the order in which they're defined, such that the first-defined + /// index has the highest priority. Further, the indexes provided by this setting are given + /// higher priority than any indexes specified via [`index_url`](#index-url) or + /// [`extra_index_url`](#extra-index-url). uv will only consider the first index that contains + /// a given package, unless an alternative [index strategy](#index-strategy) is specified. + /// + /// If an index is marked as `explicit = true`, it will be used exclusively for those + /// dependencies that select it explicitly via `[tool.uv.sources]`, as in: + /// + /// ```toml + /// [[tool.uv.index]] + /// name = "pytorch" + /// url = "https://download.pytorch.org/whl/cu121" + /// explicit = true + /// + /// [tool.uv.sources] + /// torch = { index = "pytorch" } + /// ``` + /// + /// If an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is + /// given the lowest priority when resolving packages. Additionally, marking an index as default will disable the + /// PyPI default index. + #[option( + default = "\"[]\"", + value_type = "dict", + example = r#" + [[tool.uv.index]] + name = "pytorch" + url = "https://download.pytorch.org/whl/cu121" + "# + )] + pub index: Option>, + /// The workspace definition for the project, if any. #[option_group] pub workspace: Option, + /// Whether the project is managed by uv. If `false`, uv will ignore the project when /// `uv run` is invoked. #[option( @@ -167,6 +207,7 @@ pub struct ToolUv { "# )] pub managed: Option, + /// Whether the project should be considered a Python package, or a non-package ("virtual") /// project. /// @@ -185,6 +226,7 @@ pub struct ToolUv { "# )] pub package: Option, + /// The project's development dependencies. Development dependencies will be installed by /// default in `uv run` and `uv sync`, but will not appear in the project's published metadata. #[cfg_attr( @@ -202,6 +244,7 @@ pub struct ToolUv { "# )] pub dev_dependencies: Option>>, + /// A list of supported environments against which to resolve dependencies. /// /// By default, uv will resolve for all possible environments during a `uv lock` operation. @@ -226,6 +269,7 @@ pub struct ToolUv { "# )] pub environments: Option, + /// Overrides to apply when resolving the project's dependencies. /// /// Overrides are used to force selection of a specific version of a package, regardless of the @@ -261,6 +305,7 @@ pub struct ToolUv { "# )] pub override_dependencies: Option>>, + /// Constraints to apply when resolving the project's dependencies. /// /// Constraints are used to restrict the versions of dependencies that are selected during @@ -315,7 +360,7 @@ impl ToolUvSources { impl<'de> serde::de::Deserialize<'de> for ToolUvSources { fn deserialize(deserializer: D) -> Result where - D: serde::de::Deserializer<'de>, + D: Deserializer<'de>, { struct SourcesVisitor; @@ -503,16 +548,6 @@ impl TryFrom for Sources { return Err(SourceError::EmptySources); } - // Ensure that there is at most one registry source. - if sources - .iter() - .filter(|source| matches!(source, Source::Registry { .. })) - .nth(1) - .is_some() - { - return Err(SourceError::MultipleIndexes); - } - Ok(Self(sources)) } } @@ -581,7 +616,6 @@ pub enum Source { }, /// A dependency pinned to a specific index, e.g., `torch` after setting `torch` to `https://download.pytorch.org/whl/cu118`. Registry { - // TODO(konstin): The string is more-or-less a placeholder index: String, #[serde( skip_serializing_if = "uv_pep508::marker::ser::is_empty", @@ -923,8 +957,6 @@ pub enum SourceError { OverlappingMarkers(String, String, String), #[error("Must provide at least one source")] EmptySources, - #[error("Sources can only include a single index source")] - MultipleIndexes, } impl Source { @@ -933,6 +965,7 @@ impl Source { source: RequirementSource, workspace: bool, editable: Option, + index: Option, rev: Option, tag: Option, branch: Option, @@ -973,7 +1006,19 @@ impl Source { } let source = match source { - RequirementSource::Registry { .. } => return Ok(None), + RequirementSource::Registry { index: Some(_), .. } => { + return Ok(None); + } + RequirementSource::Registry { index: None, .. } => { + if let Some(index) = index { + Source::Registry { + index, + marker: MarkerTree::TRUE, + } + } else { + return Ok(None); + } + } RequirementSource::Path { install_path, .. } | RequirementSource::Directory { install_path, .. } => Source::Path { editable, diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 75de5e001..67b3f9ae3 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -3,7 +3,8 @@ use std::path::Path; use std::str::FromStr; use std::{fmt, mem}; use thiserror::Error; -use toml_edit::{Array, DocumentMut, Item, RawString, Table, TomlError, Value}; +use toml_edit::{Array, ArrayOfTables, DocumentMut, Item, RawString, Table, TomlError, Value}; +use uv_distribution_types::Index; use uv_fs::PortablePath; use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers}; use uv_pep508::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl}; @@ -190,6 +191,88 @@ impl PyProjectTomlMut { Ok(edit) } + /// Add an [`Index`] to `tool.uv.index`. + pub fn add_index(&mut self, index: &Index) -> Result<(), Error> { + let existing = self + .doc + .entry("tool") + .or_insert(implicit()) + .as_table_mut() + .ok_or(Error::MalformedSources)? + .entry("uv") + .or_insert(implicit()) + .as_table_mut() + .ok_or(Error::MalformedSources)? + .entry("index") + .or_insert(Item::ArrayOfTables(ArrayOfTables::new())) + .as_array_of_tables() + .ok_or(Error::MalformedSources)?; + + let mut table = Table::new(); + if let Some(name) = index.name.as_ref() { + table.insert("name", toml_edit::value(name.to_string())); + } else if let Some(name) = existing + .iter() + .find(|table| { + table + .get("url") + .is_some_and(|url| url.as_str() == Some(index.url.url().as_str())) + }) + .and_then(|existing| existing.get("name")) + { + // If there's an existing index with the same URL, and a name, preserve the name. + table.insert("name", name.clone()); + } + table.insert("url", toml_edit::value(index.url.to_string())); + if index.default { + table.insert("default", toml_edit::value(true)); + } + + // Push the item to the table. + let mut updated = ArrayOfTables::new(); + updated.push(table); + for table in existing { + // If there's another index with the same name, replace it. + if table + .get("name") + .is_some_and(|name| name.as_str() == index.name.as_deref()) + { + continue; + } + + // If there's another default index, remove it. + if index.default + && table + .get("default") + .is_some_and(|default| default.as_bool() == Some(true)) + { + continue; + } + + // If there's another index with the same URL, replace it. + if table + .get("url") + .is_some_and(|url| url.as_str() == Some(index.url.url().as_str())) + { + continue; + } + + updated.push(table.clone()); + } + self.doc + .entry("tool") + .or_insert(implicit()) + .as_table_mut() + .ok_or(Error::MalformedSources)? + .entry("uv") + .or_insert(implicit()) + .as_table_mut() + .ok_or(Error::MalformedSources)? + .insert("index", Item::ArrayOfTables(updated)); + + Ok(()) + } + /// Adds a dependency to `project.optional-dependencies`. /// /// Returns `true` if the dependency was added, `false` if it was updated. diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 3b5b3f7c2..ece22fafa 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -6,15 +6,16 @@ use rustc_hash::FxHashSet; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use tracing::{debug, trace, warn}; - +use uv_distribution_types::Index; use uv_fs::{Simplified, CWD}; use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES}; use uv_pep508::{MarkerTree, RequirementOrigin, VerbatimUrl}; use uv_pypi_types::{Requirement, RequirementSource, SupportedEnvironments, VerbatimParsedUrl}; +use uv_static::EnvVars; use uv_warnings::{warn_user, warn_user_once}; use crate::pyproject::{ - Project, PyProjectToml, PyprojectTomlError, Source, Sources, ToolUvSources, ToolUvWorkspace, + Project, PyProjectToml, PyprojectTomlError, Sources, ToolUvSources, ToolUvWorkspace, }; #[derive(thiserror::Error, Debug)] @@ -79,6 +80,10 @@ pub struct Workspace { /// /// This table is overridden by the project sources. sources: BTreeMap, + /// The index table from the workspace `pyproject.toml`. + /// + /// This table is overridden by the project indexes. + indexes: Vec, /// The `pyproject.toml` of the workspace root. pyproject_toml: PyProjectToml, } @@ -266,23 +271,12 @@ impl Workspace { /// Returns the set of requirements that include all packages in the workspace. pub fn members_requirements(&self) -> impl Iterator + '_ { self.packages.values().filter_map(|member| { - let project = member.pyproject_toml.project.as_ref()?; - // Extract the extras available in the project. - let extras = project - .optional_dependencies - .as_ref() - .map(|optional_dependencies| { - // It's a `BTreeMap` so the keys are sorted. - optional_dependencies.keys().cloned().collect::>() - }) - .unwrap_or_default(); - let url = VerbatimUrl::from_absolute_path(&member.root) .expect("path is valid URL") .with_given(member.root.to_string_lossy()); Some(Requirement { - name: project.name.clone(), - extras, + name: member.pyproject_toml.project.as_ref()?.name.clone(), + extras: vec![], marker: MarkerTree::TRUE, source: if member.pyproject_toml.is_package() { RequirementSource::Directory { @@ -413,7 +407,7 @@ impl Workspace { pub fn venv(&self) -> PathBuf { /// Resolve the `UV_PROJECT_ENVIRONMENT` value, if any. fn from_project_environment_variable(workspace: &Workspace) -> Option { - let value = std::env::var_os("UV_PROJECT_ENVIRONMENT")?; + let value = std::env::var_os(EnvVars::UV_PROJECT_ENVIRONMENT)?; if value.is_empty() { return None; @@ -430,7 +424,7 @@ impl Workspace { // Resolve the `VIRTUAL_ENV` variable, if any. fn from_virtual_env_variable() -> Option { - let value = std::env::var_os("VIRTUAL_ENV")?; + let value = std::env::var_os(EnvVars::VIRTUAL_ENV)?; if value.is_empty() { return None; @@ -499,20 +493,9 @@ impl Workspace { &self.sources } - /// Returns an iterator over all sources in the workspace. - pub fn iter_sources(&self) -> impl Iterator { - self.packages - .values() - .filter_map(|member| { - member.pyproject_toml().tool.as_ref().and_then(|tool| { - tool.uv - .as_ref() - .and_then(|uv| uv.sources.as_ref()) - .map(ToolUvSources::inner) - .map(|sources| sources.values().flat_map(Sources::iter)) - }) - }) - .flatten() + /// The index table from the workspace `pyproject.toml`. + pub fn indexes(&self) -> &[Index] { + &self.indexes } /// The `pyproject.toml` of the workspace. @@ -729,11 +712,18 @@ impl Workspace { .and_then(|uv| uv.sources) .map(ToolUvSources::into_inner) .unwrap_or_default(); + let workspace_indexes = workspace_pyproject_toml + .tool + .clone() + .and_then(|tool| tool.uv) + .and_then(|uv| uv.index) + .unwrap_or_default(); Ok(Workspace { install_path: workspace_root, packages: workspace_members, sources: workspace_sources, + indexes: workspace_indexes, pyproject_toml: workspace_pyproject_toml, }) } @@ -1035,6 +1025,7 @@ impl ProjectWorkspace { // There may be package sources, but we don't need to duplicate them into the // workspace sources. sources: BTreeMap::default(), + indexes: Vec::default(), pyproject_toml: project_pyproject_toml.clone(), }, }); @@ -1534,834 +1525,4 @@ impl<'env> From<&'env VirtualProject> for InstallTarget<'env> { #[cfg(test)] #[cfg(unix)] // Avoid path escaping for the unit tests -mod tests { - use std::env; - - use std::path::Path; - - use anyhow::Result; - use assert_fs::fixture::ChildPath; - use assert_fs::prelude::*; - use insta::assert_json_snapshot; - - use crate::workspace::{DiscoveryOptions, ProjectWorkspace}; - - async fn workspace_test(folder: &str) -> (ProjectWorkspace, String) { - let root_dir = env::current_dir() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .join("scripts") - .join("workspaces"); - let project = - ProjectWorkspace::discover(&root_dir.join(folder), &DiscoveryOptions::default()) - .await - .unwrap(); - let root_escaped = regex::escape(root_dir.to_string_lossy().as_ref()); - (project, root_escaped) - } - - async fn temporary_test(folder: &Path) -> (ProjectWorkspace, String) { - let project = ProjectWorkspace::discover(folder, &DiscoveryOptions::default()) - .await - .unwrap(); - let root_escaped = regex::escape(folder.to_string_lossy().as_ref()); - (project, root_escaped) - } - - #[tokio::test] - async fn albatross_in_example() { - let (project, root_escaped) = - workspace_test("albatross-in-example/examples/bird-feeder").await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]/albatross-in-example/examples/bird-feeder", - "project_name": "bird-feeder", - "workspace": { - "install_path": "[ROOT]/albatross-in-example/examples/bird-feeder", - "packages": { - "bird-feeder": { - "root": "[ROOT]/albatross-in-example/examples/bird-feeder", - "project": { - "name": "bird-feeder", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "anyio>=4.3.0,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": {}, - "pyproject_toml": { - "project": { - "name": "bird-feeder", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "anyio>=4.3.0,<5" - ], - "optional-dependencies": null - }, - "tool": null - } - } - } - "###); - }); - } - - #[tokio::test] - async fn albatross_project_in_excluded() { - let (project, root_escaped) = - workspace_test("albatross-project-in-excluded/excluded/bird-feeder").await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder", - "project_name": "bird-feeder", - "workspace": { - "install_path": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder", - "packages": { - "bird-feeder": { - "root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder", - "project": { - "name": "bird-feeder", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "anyio>=4.3.0,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": {}, - "pyproject_toml": { - "project": { - "name": "bird-feeder", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "anyio>=4.3.0,<5" - ], - "optional-dependencies": null - }, - "tool": null - } - } - } - "###); - }); - } - - #[tokio::test] - async fn albatross_root_workspace() { - let (project, root_escaped) = workspace_test("albatross-root-workspace").await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]/albatross-root-workspace", - "project_name": "albatross", - "workspace": { - "install_path": "[ROOT]/albatross-root-workspace", - "packages": { - "albatross": { - "root": "[ROOT]/albatross-root-workspace", - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "bird-feeder", - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - }, - "bird-feeder": { - "root": "[ROOT]/albatross-root-workspace/packages/bird-feeder", - "project": { - "name": "bird-feeder", - "version": "1.0.0", - "requires-python": ">=3.8", - "dependencies": [ - "anyio>=4.3.0,<5", - "seeds" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - }, - "seeds": { - "root": "[ROOT]/albatross-root-workspace/packages/seeds", - "project": { - "name": "seeds", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "idna==3.6" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": { - "bird-feeder": [ - { - "workspace": true - } - ] - }, - "pyproject_toml": { - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "bird-feeder", - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "tool": { - "uv": { - "sources": { - "bird-feeder": [ - { - "workspace": true - } - ] - }, - "workspace": { - "members": [ - "packages/*" - ], - "exclude": null - }, - "managed": null, - "package": null, - "dev-dependencies": null, - "environments": null, - "override-dependencies": null, - "constraint-dependencies": null - } - } - } - } - } - "###); - }); - } - - #[tokio::test] - async fn albatross_virtual_workspace() { - let (project, root_escaped) = - workspace_test("albatross-virtual-workspace/packages/albatross").await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]/albatross-virtual-workspace/packages/albatross", - "project_name": "albatross", - "workspace": { - "install_path": "[ROOT]/albatross-virtual-workspace", - "packages": { - "albatross": { - "root": "[ROOT]/albatross-virtual-workspace/packages/albatross", - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "bird-feeder", - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - }, - "bird-feeder": { - "root": "[ROOT]/albatross-virtual-workspace/packages/bird-feeder", - "project": { - "name": "bird-feeder", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "anyio>=4.3.0,<5", - "seeds" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - }, - "seeds": { - "root": "[ROOT]/albatross-virtual-workspace/packages/seeds", - "project": { - "name": "seeds", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "idna==3.6" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": {}, - "pyproject_toml": { - "project": null, - "tool": { - "uv": { - "sources": null, - "workspace": { - "members": [ - "packages/*" - ], - "exclude": null - }, - "managed": null, - "package": null, - "dev-dependencies": null, - "environments": null, - "override-dependencies": null, - "constraint-dependencies": null - } - } - } - } - } - "###); - }); - } - - #[tokio::test] - async fn albatross_just_project() { - let (project, root_escaped) = workspace_test("albatross-just-project").await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]/albatross-just-project", - "project_name": "albatross", - "workspace": { - "install_path": "[ROOT]/albatross-just-project", - "packages": { - "albatross": { - "root": "[ROOT]/albatross-just-project", - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": {}, - "pyproject_toml": { - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "tool": null - } - } - } - "###); - }); - } - #[tokio::test] - async fn exclude_package() -> Result<()> { - let root = tempfile::TempDir::new()?; - let root = ChildPath::new(root.path()); - - // Create the root. - root.child("pyproject.toml").write_str( - r#" - [project] - name = "albatross" - version = "0.1.0" - requires-python = ">=3.12" - dependencies = ["tqdm>=4,<5"] - - [tool.uv.workspace] - members = ["packages/*"] - exclude = ["packages/bird-feeder"] - - [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "#, - )?; - root.child("albatross").child("__init__.py").touch()?; - - // Create an included package (`seeds`). - root.child("packages") - .child("seeds") - .child("pyproject.toml") - .write_str( - r#" - [project] - name = "seeds" - version = "1.0.0" - requires-python = ">=3.12" - dependencies = ["idna==3.6"] - - [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "#, - )?; - root.child("packages") - .child("seeds") - .child("seeds") - .child("__init__.py") - .touch()?; - - // Create an excluded package (`bird-feeder`). - root.child("packages") - .child("bird-feeder") - .child("pyproject.toml") - .write_str( - r#" - [project] - name = "bird-feeder" - version = "1.0.0" - requires-python = ">=3.12" - dependencies = ["anyio>=4.3.0,<5"] - - [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "#, - )?; - root.child("packages") - .child("bird-feeder") - .child("bird_feeder") - .child("__init__.py") - .touch()?; - - let (project, root_escaped) = temporary_test(root.as_ref()).await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]", - "project_name": "albatross", - "workspace": { - "install_path": "[ROOT]", - "packages": { - "albatross": { - "root": "[ROOT]", - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - }, - "seeds": { - "root": "[ROOT]/packages/seeds", - "project": { - "name": "seeds", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "idna==3.6" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": {}, - "pyproject_toml": { - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "tool": { - "uv": { - "sources": null, - "workspace": { - "members": [ - "packages/*" - ], - "exclude": [ - "packages/bird-feeder" - ] - }, - "managed": null, - "package": null, - "dev-dependencies": null, - "environments": null, - "override-dependencies": null, - "constraint-dependencies": null - } - } - } - } - } - "###); - }); - - // Rewrite the members to both include and exclude `bird-feeder` by name. - root.child("pyproject.toml").write_str( - r#" - [project] - name = "albatross" - version = "0.1.0" - requires-python = ">=3.12" - dependencies = ["tqdm>=4,<5"] - - [tool.uv.workspace] - members = ["packages/seeds", "packages/bird-feeder"] - exclude = ["packages/bird-feeder"] - - [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "#, - )?; - - // `bird-feeder` should still be excluded. - let (project, root_escaped) = temporary_test(root.as_ref()).await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]", - "project_name": "albatross", - "workspace": { - "install_path": "[ROOT]", - "packages": { - "albatross": { - "root": "[ROOT]", - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - }, - "seeds": { - "root": "[ROOT]/packages/seeds", - "project": { - "name": "seeds", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "idna==3.6" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": {}, - "pyproject_toml": { - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "tool": { - "uv": { - "sources": null, - "workspace": { - "members": [ - "packages/seeds", - "packages/bird-feeder" - ], - "exclude": [ - "packages/bird-feeder" - ] - }, - "managed": null, - "package": null, - "dev-dependencies": null, - "environments": null, - "override-dependencies": null, - "constraint-dependencies": null - } - } - } - } - } - "###); - }); - - // Rewrite the exclusion to use the top-level directory (`packages`). - root.child("pyproject.toml").write_str( - r#" - [project] - name = "albatross" - version = "0.1.0" - requires-python = ">=3.12" - dependencies = ["tqdm>=4,<5"] - - [tool.uv.workspace] - members = ["packages/seeds", "packages/bird-feeder"] - exclude = ["packages"] - - [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "#, - )?; - - // `bird-feeder` should now be included. - let (project, root_escaped) = temporary_test(root.as_ref()).await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]", - "project_name": "albatross", - "workspace": { - "install_path": "[ROOT]", - "packages": { - "albatross": { - "root": "[ROOT]", - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - }, - "bird-feeder": { - "root": "[ROOT]/packages/bird-feeder", - "project": { - "name": "bird-feeder", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "anyio>=4.3.0,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - }, - "seeds": { - "root": "[ROOT]/packages/seeds", - "project": { - "name": "seeds", - "version": "1.0.0", - "requires-python": ">=3.12", - "dependencies": [ - "idna==3.6" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": {}, - "pyproject_toml": { - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "tool": { - "uv": { - "sources": null, - "workspace": { - "members": [ - "packages/seeds", - "packages/bird-feeder" - ], - "exclude": [ - "packages" - ] - }, - "managed": null, - "package": null, - "dev-dependencies": null, - "environments": null, - "override-dependencies": null, - "constraint-dependencies": null - } - } - } - } - } - "###); - }); - - // Rewrite the exclusion to use the top-level directory with a glob (`packages/*`). - root.child("pyproject.toml").write_str( - r#" - [project] - name = "albatross" - version = "0.1.0" - requires-python = ">=3.12" - dependencies = ["tqdm>=4,<5"] - - [tool.uv.workspace] - members = ["packages/seeds", "packages/bird-feeder"] - exclude = ["packages/*"] - - [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "#, - )?; - - // `bird-feeder` and `seeds` should now be excluded. - let (project, root_escaped) = temporary_test(root.as_ref()).await; - let filters = vec![(root_escaped.as_str(), "[ROOT]")]; - insta::with_settings!({filters => filters}, { - assert_json_snapshot!( - project, - { - ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" - }, - @r###" - { - "project_root": "[ROOT]", - "project_name": "albatross", - "workspace": { - "install_path": "[ROOT]", - "packages": { - "albatross": { - "root": "[ROOT]", - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "pyproject_toml": "[PYPROJECT_TOML]" - } - }, - "sources": {}, - "pyproject_toml": { - "project": { - "name": "albatross", - "version": "0.1.0", - "requires-python": ">=3.12", - "dependencies": [ - "tqdm>=4,<5" - ], - "optional-dependencies": null - }, - "tool": { - "uv": { - "sources": null, - "workspace": { - "members": [ - "packages/seeds", - "packages/bird-feeder" - ], - "exclude": [ - "packages/*" - ] - }, - "managed": null, - "package": null, - "dev-dependencies": null, - "environments": null, - "override-dependencies": null, - "constraint-dependencies": null - } - } - } - } - } - "###); - }); - - Ok(()) - } -} +mod tests; diff --git a/crates/uv-workspace/src/workspace/tests.rs b/crates/uv-workspace/src/workspace/tests.rs new file mode 100644 index 000000000..82978abde --- /dev/null +++ b/crates/uv-workspace/src/workspace/tests.rs @@ -0,0 +1,842 @@ +use std::env; + +use std::path::Path; + +use anyhow::Result; +use assert_fs::fixture::ChildPath; +use assert_fs::prelude::*; +use insta::assert_json_snapshot; + +use crate::workspace::{DiscoveryOptions, ProjectWorkspace}; + +async fn workspace_test(folder: &str) -> (ProjectWorkspace, String) { + let root_dir = env::current_dir() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .join("scripts") + .join("workspaces"); + let project = ProjectWorkspace::discover(&root_dir.join(folder), &DiscoveryOptions::default()) + .await + .unwrap(); + let root_escaped = regex::escape(root_dir.to_string_lossy().as_ref()); + (project, root_escaped) +} + +async fn temporary_test(folder: &Path) -> (ProjectWorkspace, String) { + let project = ProjectWorkspace::discover(folder, &DiscoveryOptions::default()) + .await + .unwrap(); + let root_escaped = regex::escape(folder.to_string_lossy().as_ref()); + (project, root_escaped) +} + +#[tokio::test] +async fn albatross_in_example() { + let (project, root_escaped) = workspace_test("albatross-in-example/examples/bird-feeder").await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]/albatross-in-example/examples/bird-feeder", + "project_name": "bird-feeder", + "workspace": { + "install_path": "[ROOT]/albatross-in-example/examples/bird-feeder", + "packages": { + "bird-feeder": { + "root": "[ROOT]/albatross-in-example/examples/bird-feeder", + "project": { + "name": "bird-feeder", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "anyio>=4.3.0,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": {}, + "indexes": [], + "pyproject_toml": { + "project": { + "name": "bird-feeder", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "anyio>=4.3.0,<5" + ], + "optional-dependencies": null + }, + "tool": null + } + } + } + "###); + }); +} + +#[tokio::test] +async fn albatross_project_in_excluded() { + let (project, root_escaped) = + workspace_test("albatross-project-in-excluded/excluded/bird-feeder").await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder", + "project_name": "bird-feeder", + "workspace": { + "install_path": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder", + "packages": { + "bird-feeder": { + "root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder", + "project": { + "name": "bird-feeder", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "anyio>=4.3.0,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": {}, + "indexes": [], + "pyproject_toml": { + "project": { + "name": "bird-feeder", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "anyio>=4.3.0,<5" + ], + "optional-dependencies": null + }, + "tool": null + } + } + } + "###); + }); +} + +#[tokio::test] +async fn albatross_root_workspace() { + let (project, root_escaped) = workspace_test("albatross-root-workspace").await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]/albatross-root-workspace", + "project_name": "albatross", + "workspace": { + "install_path": "[ROOT]/albatross-root-workspace", + "packages": { + "albatross": { + "root": "[ROOT]/albatross-root-workspace", + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "bird-feeder", + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + }, + "bird-feeder": { + "root": "[ROOT]/albatross-root-workspace/packages/bird-feeder", + "project": { + "name": "bird-feeder", + "version": "1.0.0", + "requires-python": ">=3.8", + "dependencies": [ + "anyio>=4.3.0,<5", + "seeds" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + }, + "seeds": { + "root": "[ROOT]/albatross-root-workspace/packages/seeds", + "project": { + "name": "seeds", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "idna==3.6" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": { + "bird-feeder": [ + { + "workspace": true + } + ] + }, + "indexes": [], + "pyproject_toml": { + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "bird-feeder", + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "tool": { + "uv": { + "sources": { + "bird-feeder": [ + { + "workspace": true + } + ] + }, + "index": null, + "workspace": { + "members": [ + "packages/*" + ], + "exclude": null + }, + "managed": null, + "package": null, + "dev-dependencies": null, + "environments": null, + "override-dependencies": null, + "constraint-dependencies": null + } + } + } + } + } + "###); + }); +} + +#[tokio::test] +async fn albatross_virtual_workspace() { + let (project, root_escaped) = + workspace_test("albatross-virtual-workspace/packages/albatross").await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]/albatross-virtual-workspace/packages/albatross", + "project_name": "albatross", + "workspace": { + "install_path": "[ROOT]/albatross-virtual-workspace", + "packages": { + "albatross": { + "root": "[ROOT]/albatross-virtual-workspace/packages/albatross", + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "bird-feeder", + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + }, + "bird-feeder": { + "root": "[ROOT]/albatross-virtual-workspace/packages/bird-feeder", + "project": { + "name": "bird-feeder", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "anyio>=4.3.0,<5", + "seeds" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + }, + "seeds": { + "root": "[ROOT]/albatross-virtual-workspace/packages/seeds", + "project": { + "name": "seeds", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "idna==3.6" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": {}, + "indexes": [], + "pyproject_toml": { + "project": null, + "tool": { + "uv": { + "sources": null, + "index": null, + "workspace": { + "members": [ + "packages/*" + ], + "exclude": null + }, + "managed": null, + "package": null, + "dev-dependencies": null, + "environments": null, + "override-dependencies": null, + "constraint-dependencies": null + } + } + } + } + } + "###); + }); +} + +#[tokio::test] +async fn albatross_just_project() { + let (project, root_escaped) = workspace_test("albatross-just-project").await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]/albatross-just-project", + "project_name": "albatross", + "workspace": { + "install_path": "[ROOT]/albatross-just-project", + "packages": { + "albatross": { + "root": "[ROOT]/albatross-just-project", + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": {}, + "indexes": [], + "pyproject_toml": { + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "tool": null + } + } + } + "###); + }); +} +#[tokio::test] +async fn exclude_package() -> Result<()> { + let root = tempfile::TempDir::new()?; + let root = ChildPath::new(root.path()); + + // Create the root. + root.child("pyproject.toml").write_str( + r#" + [project] + name = "albatross" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["tqdm>=4,<5"] + + [tool.uv.workspace] + members = ["packages/*"] + exclude = ["packages/bird-feeder"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + root.child("albatross").child("__init__.py").touch()?; + + // Create an included package (`seeds`). + root.child("packages") + .child("seeds") + .child("pyproject.toml") + .write_str( + r#" + [project] + name = "seeds" + version = "1.0.0" + requires-python = ">=3.12" + dependencies = ["idna==3.6"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + root.child("packages") + .child("seeds") + .child("seeds") + .child("__init__.py") + .touch()?; + + // Create an excluded package (`bird-feeder`). + root.child("packages") + .child("bird-feeder") + .child("pyproject.toml") + .write_str( + r#" + [project] + name = "bird-feeder" + version = "1.0.0" + requires-python = ">=3.12" + dependencies = ["anyio>=4.3.0,<5"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + root.child("packages") + .child("bird-feeder") + .child("bird_feeder") + .child("__init__.py") + .touch()?; + + let (project, root_escaped) = temporary_test(root.as_ref()).await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]", + "project_name": "albatross", + "workspace": { + "install_path": "[ROOT]", + "packages": { + "albatross": { + "root": "[ROOT]", + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + }, + "seeds": { + "root": "[ROOT]/packages/seeds", + "project": { + "name": "seeds", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "idna==3.6" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": {}, + "indexes": [], + "pyproject_toml": { + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "tool": { + "uv": { + "sources": null, + "index": null, + "workspace": { + "members": [ + "packages/*" + ], + "exclude": [ + "packages/bird-feeder" + ] + }, + "managed": null, + "package": null, + "dev-dependencies": null, + "environments": null, + "override-dependencies": null, + "constraint-dependencies": null + } + } + } + } + } + "###); + }); + + // Rewrite the members to both include and exclude `bird-feeder` by name. + root.child("pyproject.toml").write_str( + r#" + [project] + name = "albatross" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["tqdm>=4,<5"] + + [tool.uv.workspace] + members = ["packages/seeds", "packages/bird-feeder"] + exclude = ["packages/bird-feeder"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + + // `bird-feeder` should still be excluded. + let (project, root_escaped) = temporary_test(root.as_ref()).await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]", + "project_name": "albatross", + "workspace": { + "install_path": "[ROOT]", + "packages": { + "albatross": { + "root": "[ROOT]", + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + }, + "seeds": { + "root": "[ROOT]/packages/seeds", + "project": { + "name": "seeds", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "idna==3.6" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": {}, + "indexes": [], + "pyproject_toml": { + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "tool": { + "uv": { + "sources": null, + "index": null, + "workspace": { + "members": [ + "packages/seeds", + "packages/bird-feeder" + ], + "exclude": [ + "packages/bird-feeder" + ] + }, + "managed": null, + "package": null, + "dev-dependencies": null, + "environments": null, + "override-dependencies": null, + "constraint-dependencies": null + } + } + } + } + } + "###); + }); + + // Rewrite the exclusion to use the top-level directory (`packages`). + root.child("pyproject.toml").write_str( + r#" + [project] + name = "albatross" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["tqdm>=4,<5"] + + [tool.uv.workspace] + members = ["packages/seeds", "packages/bird-feeder"] + exclude = ["packages"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + + // `bird-feeder` should now be included. + let (project, root_escaped) = temporary_test(root.as_ref()).await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]", + "project_name": "albatross", + "workspace": { + "install_path": "[ROOT]", + "packages": { + "albatross": { + "root": "[ROOT]", + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + }, + "bird-feeder": { + "root": "[ROOT]/packages/bird-feeder", + "project": { + "name": "bird-feeder", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "anyio>=4.3.0,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + }, + "seeds": { + "root": "[ROOT]/packages/seeds", + "project": { + "name": "seeds", + "version": "1.0.0", + "requires-python": ">=3.12", + "dependencies": [ + "idna==3.6" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": {}, + "indexes": [], + "pyproject_toml": { + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "tool": { + "uv": { + "sources": null, + "index": null, + "workspace": { + "members": [ + "packages/seeds", + "packages/bird-feeder" + ], + "exclude": [ + "packages" + ] + }, + "managed": null, + "package": null, + "dev-dependencies": null, + "environments": null, + "override-dependencies": null, + "constraint-dependencies": null + } + } + } + } + } + "###); + }); + + // Rewrite the exclusion to use the top-level directory with a glob (`packages/*`). + root.child("pyproject.toml").write_str( + r#" + [project] + name = "albatross" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["tqdm>=4,<5"] + + [tool.uv.workspace] + members = ["packages/seeds", "packages/bird-feeder"] + exclude = ["packages/*"] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#, + )?; + + // `bird-feeder` and `seeds` should now be excluded. + let (project, root_escaped) = temporary_test(root.as_ref()).await; + let filters = vec![(root_escaped.as_str(), "[ROOT]")]; + insta::with_settings!({filters => filters}, { + assert_json_snapshot!( + project, + { + ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" + }, + @r###" + { + "project_root": "[ROOT]", + "project_name": "albatross", + "workspace": { + "install_path": "[ROOT]", + "packages": { + "albatross": { + "root": "[ROOT]", + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "pyproject_toml": "[PYPROJECT_TOML]" + } + }, + "sources": {}, + "indexes": [], + "pyproject_toml": { + "project": { + "name": "albatross", + "version": "0.1.0", + "requires-python": ">=3.12", + "dependencies": [ + "tqdm>=4,<5" + ], + "optional-dependencies": null + }, + "tool": { + "uv": { + "sources": null, + "index": null, + "workspace": { + "members": [ + "packages/seeds", + "packages/bird-feeder" + ], + "exclude": [ + "packages/*" + ] + }, + "managed": null, + "package": null, + "dev-dependencies": null, + "environments": null, + "override-dependencies": null, + "constraint-dependencies": null + } + } + } + } + } + "###); + }); + + Ok(()) +} diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index eaf90c58f..155689022 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.4.18" +version = "0.4.23" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } @@ -15,12 +15,14 @@ workspace = true [dependencies] uv-auth = { workspace = true } +uv-build-backend = { workspace = true } uv-cache = { workspace = true } uv-cache-info = { workspace = true } uv-cache-key = { workspace = true } uv-cli = { workspace = true } uv-client = { workspace = true } uv-configuration = { workspace = true } +uv-console = { workspace = true } uv-dispatch = { workspace = true } uv-distribution = { workspace = true } uv-distribution-filename = { workspace = true } @@ -44,9 +46,11 @@ uv-resolver = { workspace = true } uv-scripts = { workspace = true } uv-settings = { workspace = true, features = ["schemars"] } uv-shell = { workspace = true } +uv-static = { workspace = true } uv-tool = { workspace = true } uv-types = { workspace = true } uv-virtualenv = { workspace = true } +uv-version = { workspace = true } uv-warnings = { workspace = true } uv-workspace = { workspace = true } @@ -57,6 +61,7 @@ axoupdater = { workspace = true, features = [ "tokio", ], optional = true } clap = { workspace = true, features = ["derive", "string", "wrap_help"] } +console = { workspace = true } ctrlc = { workspace = true } flate2 = { workspace = true, default-features = false } fs-err = { workspace = true, features = ["tokio"] } @@ -71,6 +76,7 @@ miette = { workspace = true, features = ["fancy-no-backtrace"] } owo-colors = { workspace = true } rayon = { workspace = true } regex = { workspace = true } +reqwest = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/uv/src/commands/build_backend.rs b/crates/uv/src/commands/build_backend.rs index 57ecd0c25..a49a389e6 100644 --- a/crates/uv/src/commands/build_backend.rs +++ b/crates/uv/src/commands/build_backend.rs @@ -1,16 +1,25 @@ +#![allow(clippy::print_stdout)] + use crate::commands::ExitStatus; use anyhow::Result; +use std::env; use std::path::Path; pub(crate) fn build_sdist(_sdist_directory: &Path) -> Result { todo!() } - pub(crate) fn build_wheel( - _wheel_directory: &Path, - _metadata_directory: Option<&Path>, + wheel_directory: &Path, + metadata_directory: Option<&Path>, ) -> Result { - todo!() + let filename = uv_build_backend::build( + &env::current_dir()?, + wheel_directory, + metadata_directory, + uv_version::version(), + )?; + println!("{filename}"); + Ok(ExitStatus::Success) } pub(crate) fn build_editable( @@ -27,9 +36,14 @@ pub(crate) fn get_requires_for_build_sdist() -> Result { pub(crate) fn get_requires_for_build_wheel() -> Result { todo!() } - -pub(crate) fn prepare_metadata_for_build_wheel(_wheel_directory: &Path) -> Result { - todo!() +pub(crate) fn prepare_metadata_for_build_wheel(metadata_directory: &Path) -> Result { + let filename = uv_build_backend::metadata( + &env::current_dir()?, + metadata_directory, + uv_version::version(), + )?; + println!("{filename}"); + Ok(ExitStatus::Success) } pub(crate) fn get_requires_for_build_editable() -> Result { diff --git a/crates/uv/src/commands/build.rs b/crates/uv/src/commands/build_frontend.rs similarity index 95% rename from crates/uv/src/commands/build.rs rename to crates/uv/src/commands/build_frontend.rs index 0ecabe614..fa4e44bf4 100644 --- a/crates/uv/src/commands/build.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -5,17 +5,18 @@ use std::io::Write as _; use std::path::{Path, PathBuf}; use anyhow::Result; + use owo_colors::OwoColorize; use uv_distribution_filename::SourceDistExtension; -use uv_distribution_types::{DependencyMetadata, IndexLocations}; +use uv_distribution_types::{DependencyMetadata, Index, IndexLocations}; use uv_install_wheel::linker::LinkMode; -use uv_auth::store_credentials_from_url; -use uv_cache::Cache; +use uv_auth::store_credentials; +use uv_cache::{Cache, CacheBucket}; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildKind, BuildOptions, BuildOutput, Concurrency, ConfigSettings, Constraints, - HashCheckingMode, IndexStrategy, KeyringProviderType, SourceStrategy, TrustedHost, + HashCheckingMode, IndexStrategy, KeyringProviderType, LowerBound, SourceStrategy, TrustedHost, }; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; @@ -38,7 +39,7 @@ use crate::settings::{ResolverSettings, ResolverSettingsRef}; /// Build source distributions and wheels. #[allow(clippy::fn_params_excessive_bools)] -pub(crate) async fn build( +pub(crate) async fn build_frontend( project_dir: &Path, src: Option, package: Option, @@ -150,7 +151,7 @@ async fn build_impl( let src = std::path::absolute(src)?; let metadata = match fs_err::tokio::metadata(&src).await { Ok(metadata) => metadata, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + Err(err) if err.kind() == io::ErrorKind::NotFound => { return Err(anyhow::anyhow!( "Source `{}` does not exist", src.user_display() @@ -400,8 +401,10 @@ async fn build_package( .into_interpreter(); // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + store_credentials(index.raw_url(), credentials); + } } // Read build constraints. @@ -454,7 +457,9 @@ async fn build_package( // 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?; + let entries = client + .fetch(index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, None, &hasher, build_options) }; @@ -481,6 +486,7 @@ async fn build_package( build_options, &hasher, exclude_newer, + LowerBound::Allow, sources, concurrency, ); @@ -529,9 +535,9 @@ async fn build_package( }; // Prepare some common arguments for the build. + let dist = None; let subdirectory = None; let version_id = source.path().file_name().and_then(|name| name.to_str()); - let dist = None; let build_output = match printer { Printer::Default | Printer::NoProgress | Printer::Verbose => { @@ -557,8 +563,10 @@ async fn build_package( .setup_build( source.path(), subdirectory, + source.path(), version_id.map(ToString::to_string), dist, + sources, BuildKind::Sdist, build_output, ) @@ -571,7 +579,7 @@ async fn build_package( let ext = SourceDistExtension::from_path(path.as_path()).map_err(|err| { anyhow::anyhow!("`{}` is not a valid source distribution, as it ends with an unsupported extension. Expected one of: {err}.", path.user_display()) })?; - let temp_dir = tempfile::tempdir_in(&output_dir)?; + let temp_dir = tempfile::tempdir_in(cache.bucket(CacheBucket::SourceDistributions))?; uv_extract::stream::archive(reader, ext, temp_dir.path()).await?; // Extract the top-level directory from the archive. @@ -594,8 +602,10 @@ async fn build_package( .setup_build( &extracted, subdirectory, + source.path(), version_id.map(ToString::to_string), dist, + sources, BuildKind::Wheel, build_output, ) @@ -615,8 +625,10 @@ async fn build_package( .setup_build( source.path(), subdirectory, + source.path(), version_id.map(ToString::to_string), dist, + sources, BuildKind::Sdist, build_output, ) @@ -636,8 +648,10 @@ async fn build_package( .setup_build( source.path(), subdirectory, + source.path(), version_id.map(ToString::to_string), dist, + sources, BuildKind::Wheel, build_output, ) @@ -656,8 +670,10 @@ async fn build_package( .setup_build( source.path(), subdirectory, + source.path(), version_id.map(ToString::to_string), dist, + sources, BuildKind::Sdist, build_output, ) @@ -673,8 +689,10 @@ async fn build_package( .setup_build( source.path(), subdirectory, + source.path(), version_id.map(ToString::to_string), dist, + sources, BuildKind::Wheel, build_output, ) @@ -712,8 +730,10 @@ async fn build_package( .setup_build( &extracted, subdirectory, + source.path(), version_id.map(ToString::to_string), dist, + sources, BuildKind::Wheel, build_output, ) diff --git a/crates/uv/src/commands/help.rs b/crates/uv/src/commands/help.rs index 71025148a..954f36a42 100644 --- a/crates/uv/src/commands/help.rs +++ b/crates/uv/src/commands/help.rs @@ -13,6 +13,7 @@ use which::which; use super::ExitStatus; use crate::printer::Printer; use uv_cli::Cli; +use uv_static::EnvVars; // hidden subcommands to show in the help command const SHOW_HIDDEN_COMMANDS: &[&str] = &["generate-shell-completion"]; @@ -221,7 +222,7 @@ impl Pager { /// Supports the `PAGER` environment variable, otherwise checks for `less` and `more` in the /// search path. fn try_from_env() -> Option { - if let Some(pager) = std::env::var_os("PAGER") { + if let Some(pager) = std::env::var_os(EnvVars::PAGER) { if !pager.is_empty() { return Pager::from_str(&pager.to_string_lossy()).ok(); } diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index 572cda1b3..b660d6dc4 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -7,7 +7,7 @@ use std::path::Path; use std::time::Duration; use std::{fmt::Display, fmt::Write, process::ExitCode}; -pub(crate) use build::build; +pub(crate) use build_frontend::build_frontend; pub(crate) use cache_clean::cache_clean; pub(crate) use cache_dir::cache_dir; pub(crate) use cache_prune::cache_prune; @@ -60,8 +60,8 @@ pub(crate) use version::version; use crate::printer::Printer; -mod build; pub(crate) mod build_backend; +mod build_frontend; mod cache_clean; mod cache_dir; mod cache_prune; diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index cbeaa2311..aa2c4de3b 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -4,20 +4,20 @@ use std::path::Path; use anyhow::{anyhow, Result}; use itertools::Itertools; use owo_colors::OwoColorize; +use rustc_hash::FxHashSet; use tracing::debug; -use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, IndexStrategy, - NoBinary, NoBuild, Reinstall, SourceStrategy, TrustedHost, Upgrade, + LowerBound, NoBinary, NoBuild, Reinstall, SourceStrategy, TrustedHost, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::BuildDispatch; use uv_distribution_types::{ - DependencyMetadata, IndexCapabilities, IndexLocations, NameRequirementSpecification, - UnresolvedRequirementSpecification, Verbatim, + DependencyMetadata, Index, IndexCapabilities, IndexLocations, NameRequirementSpecification, + Origin, UnresolvedRequirementSpecification, Verbatim, }; use uv_fs::Simplified; use uv_git::GitResolver; @@ -273,12 +273,26 @@ pub(crate) async fn pip_compile( let dev = Vec::default(); // Incorporate any index locations from the provided sources. - let index_locations = - index_locations.combine(index_url, extra_index_urls, find_links, no_index); + let index_locations = index_locations.combine( + extra_index_urls + .into_iter() + .map(Index::from_extra_index_url) + .chain(index_url.map(Index::from_index_url)) + .map(|index| index.with_origin(Origin::RequirementsTxt)) + .collect(), + find_links + .into_iter() + .map(Index::from_find_links) + .map(|index| index.with_origin(Origin::RequirementsTxt)) + .collect(), + no_index, + ); // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -301,7 +315,9 @@ pub(crate) async fn pip_compile( // 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?; + let entries = client + .fetch(index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, tags.as_deref(), &hasher, &build_options) }; @@ -347,6 +363,7 @@ pub(crate) async fn pip_compile( &build_options, &build_hashes, exclude_newer, + LowerBound::Warn, sources, concurrency, ); @@ -446,20 +463,23 @@ pub(crate) async fn pip_compile( // If necessary, include the `--index-url` and `--extra-index-url` locations. if include_index_url { - if let Some(index) = index_locations.index() { - writeln!(writer, "--index-url {}", index.verbatim())?; + if let Some(index) = index_locations.default_index() { + writeln!(writer, "--index-url {}", index.url().verbatim())?; wrote_preamble = true; } - for extra_index in index_locations.extra_index() { - writeln!(writer, "--extra-index-url {}", extra_index.verbatim())?; - wrote_preamble = true; + let mut seen = FxHashSet::default(); + for extra_index in index_locations.implicit_indexes() { + if seen.insert(extra_index.url()) { + writeln!(writer, "--extra-index-url {}", extra_index.url().verbatim())?; + wrote_preamble = true; + } } } // If necessary, include the `--find-links` locations. if include_find_links { - for flat_index in index_locations.flat_index() { - writeln!(writer, "--find-links {}", flat_index.verbatim())?; + for flat_index in index_locations.flat_indexes() { + writeln!(writer, "--find-links {}", flat_index.url().verbatim())?; wrote_preamble = true; } } diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index bd31e1b73..01db48523 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -4,17 +4,16 @@ use itertools::Itertools; use owo_colors::OwoColorize; use tracing::{debug, enabled, Level}; -use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, HashCheckingMode, - IndexStrategy, Reinstall, SourceStrategy, TrustedHost, Upgrade, + IndexStrategy, LowerBound, Reinstall, SourceStrategy, TrustedHost, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::BuildDispatch; use uv_distribution_types::{ - DependencyMetadata, IndexLocations, NameRequirementSpecification, Resolution, + DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, Origin, Resolution, UnresolvedRequirementSpecification, }; use uv_fs::Simplified; @@ -67,6 +66,7 @@ pub(crate) async fn pip_install( no_build_isolation: bool, no_build_isolation_package: Vec, build_options: BuildOptions, + modifications: Modifications, python_version: Option, python_platform: Option, strict: bool, @@ -203,7 +203,12 @@ pub(crate) async fn pip_install( // Check if the current environment satisfies the requirements. // Ideally, the resolver would be fast enough to let us remove this check. But right now, for large environments, // it's an order of magnitude faster to validate the environment than to resolve the requirements. - if reinstall.is_none() && upgrade.is_none() && source_trees.is_empty() && overrides.is_empty() { + if reinstall.is_none() + && upgrade.is_none() + && source_trees.is_empty() + && overrides.is_empty() + && matches!(modifications, Modifications::Sufficient) + { match site_packages.satisfies(&requirements, &constraints, &markers)? { // If the requirements are already satisfied, we're done. SatisfiesResult::Fresh { @@ -222,6 +227,7 @@ pub(crate) async fn pip_install( if dry_run { writeln!(printer.stderr(), "Would make no changes")?; } + return Ok(ExitStatus::Success); } SatisfiesResult::Unsatisfied(requirement) => { @@ -268,12 +274,26 @@ pub(crate) async fn pip_install( let dev = Vec::default(); // Incorporate any index locations from the provided sources. - let index_locations = - index_locations.combine(index_url, extra_index_urls, find_links, no_index); + let index_locations = index_locations.combine( + extra_index_urls + .into_iter() + .map(Index::from_extra_index_url) + .chain(index_url.map(Index::from_index_url)) + .map(|index| index.with_origin(Origin::RequirementsTxt)) + .collect(), + find_links + .into_iter() + .map(Index::from_find_links) + .map(|index| index.with_origin(Origin::RequirementsTxt)) + .collect(), + no_index, + ); // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -291,7 +311,9 @@ pub(crate) async fn pip_install( // 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?; + let entries = client + .fetch(index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, Some(&tags), &hasher, &build_options) }; @@ -347,6 +369,7 @@ pub(crate) async fn pip_install( &build_options, &build_hasher, exclude_newer, + LowerBound::Warn, sources, concurrency, ); @@ -408,7 +431,7 @@ pub(crate) async fn pip_install( operations::install( &resolution, site_packages, - Modifications::Sufficient, + modifications, &reinstall, &build_options, link_mode, diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 406c3f9a1..7b30ece08 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -134,13 +134,12 @@ pub(crate) async fn resolve( if !unnamed.is_empty() { requirements.extend( NamedRequirementsResolver::new( - unnamed, hasher, index, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), ) .with_reporter(ResolverReporter::from(printer)) - .resolve() + .resolve(unnamed.into_iter()) .await?, ); } @@ -148,14 +147,13 @@ pub(crate) async fn resolve( // Resolve any source trees into requirements. if !source_trees.is_empty() { let resolutions = SourceTreeResolver::new( - source_trees, extras, hasher, index, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), ) .with_reporter(ResolverReporter::from(printer)) - .resolve() + .resolve(source_trees.iter().map(PathBuf::as_path)) .await?; // If we resolved a single project, use it for the project name. @@ -219,13 +217,12 @@ pub(crate) async fn resolve( if !unnamed.is_empty() { overrides.extend( NamedRequirementsResolver::new( - unnamed, hasher, index, DistributionDatabase::new(client, build_dispatch, concurrency.downloads), ) .with_reporter(ResolverReporter::from(printer)) - .resolve() + .resolve(unnamed.into_iter()) .await?, ); } @@ -770,10 +767,7 @@ pub(crate) enum Error { Fmt(#[from] std::fmt::Error), #[error(transparent)] - Lookahead(#[from] uv_requirements::LookaheadError), - - #[error(transparent)] - Named(#[from] uv_requirements::NamedRequirementsError), + Requirements(#[from] uv_requirements::Error), #[error(transparent)] Anyhow(#[from] anyhow::Error), diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 3d49dbf7f..87f40f750 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -4,16 +4,15 @@ use anyhow::Result; use owo_colors::OwoColorize; use tracing::debug; -use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, HashCheckingMode, - IndexStrategy, Reinstall, SourceStrategy, TrustedHost, Upgrade, + IndexStrategy, LowerBound, Reinstall, SourceStrategy, TrustedHost, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::BuildDispatch; -use uv_distribution_types::{DependencyMetadata, IndexLocations, Resolution}; +use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Origin, Resolution}; use uv_fs::Simplified; use uv_install_wheel::linker::LinkMode; use uv_installer::SitePackages; @@ -211,12 +210,26 @@ pub(crate) async fn pip_sync( }; // Incorporate any index locations from the provided sources. - let index_locations = - index_locations.combine(index_url, extra_index_urls, find_links, no_index); + let index_locations = index_locations.combine( + extra_index_urls + .into_iter() + .map(Index::from_extra_index_url) + .chain(index_url.map(Index::from_index_url)) + .map(|index| index.with_origin(Origin::RequirementsTxt)) + .collect(), + find_links + .into_iter() + .map(Index::from_find_links) + .map(|index| index.with_origin(Origin::RequirementsTxt)) + .collect(), + no_index, + ); // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -234,7 +247,9 @@ pub(crate) async fn pip_sync( // 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?; + let entries = client + .fetch(index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, Some(&tags), &hasher, &build_options) }; @@ -296,6 +311,7 @@ pub(crate) async fn pip_sync( &build_options, &build_hasher, exclude_newer, + LowerBound::Warn, sources, concurrency, ); diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index d40cc7091..2792cbe9f 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -1,23 +1,24 @@ +use std::collections::hash_map::Entry; +use std::fmt::Write; +use std::path::{Path, PathBuf}; + use anyhow::{bail, Context, Result}; use itertools::Itertools; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashMap}; -use std::collections::hash_map::Entry; -use std::fmt::Write; -use std::path::{Path, PathBuf}; use tracing::debug; +use url::Url; -use uv_auth::{store_credentials_from_url, Credentials}; use uv_cache::Cache; use uv_cache_key::RepositoryUrl; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DevMode, EditableMode, ExtrasSpecification, InstallOptions, - SourceStrategy, + LowerBound, SourceStrategy, }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; -use uv_distribution_types::UnresolvedRequirement; +use uv_distribution_types::{Index, UnresolvedRequirement, VersionId}; use uv_fs::Simplified; use uv_git::{GitReference, GIT_STORE}; use uv_normalize::PackageName; @@ -58,6 +59,7 @@ pub(crate) async fn add( editable: Option, dependency_type: DependencyType, raw_sources: bool, + indexes: Vec, rev: Option, tag: Option, branch: Option, @@ -115,7 +117,7 @@ pub(crate) async fn add( } if no_sync { warn_user_once!( - "`--no_sync` is a no-op for Python scripts with inline metadata, which always run in isolation" + "`--no-sync` is a no-op for Python scripts with inline metadata, which always run in isolation" ); } @@ -232,6 +234,7 @@ pub(crate) async fn add( // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. + let bounds = LowerBound::default(); let build_constraints = Constraints::default(); let build_hasher = HashStrategy::default(); let hasher = HashStrategy::default(); @@ -244,8 +247,10 @@ pub(crate) async fn add( resolution_environment(python_version, python_platform, target.interpreter())?; // Add all authenticated sources to the cache. - for url in settings.index_locations.urls() { - store_credentials_from_url(url); + for index in settings.index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -274,7 +279,9 @@ pub(crate) async fn add( // Resolve the flat indexes from `--find-links`. let flat_index = { let client = FlatIndexClient::new(&client, cache); - let entries = client.fetch(settings.index_locations.flat_index()).await?; + let entries = client + .fetch(settings.index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, Some(&tags), &hasher, &settings.build_options) }; @@ -298,6 +305,7 @@ pub(crate) async fn add( &settings.build_options, &build_hasher, settings.exclude_newer, + bounds, sources, concurrency, ); @@ -326,13 +334,12 @@ pub(crate) async fn add( if !unnamed.is_empty() { requirements.extend( NamedRequirementsResolver::new( - unnamed, &hasher, &state.index, DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), ) .with_reporter(ResolverReporter::from(printer)) - .resolve() + .resolve(unnamed.into_iter()) .await?, ); } @@ -341,16 +348,13 @@ pub(crate) async fn add( }; // If any of the requirements are self-dependencies, bail. - if matches!( - dependency_type, - DependencyType::Production | DependencyType::Dev - ) { + if matches!(dependency_type, DependencyType::Production) { if let Target::Project(project, _) = &target { if let Some(project_name) = project.project_name() { for requirement in &requirements { if requirement.name == *project_name { bail!( - "Requirement name `{}` matches project name `{}`, but self-dependencies are not permitted. If your project name (`{}`) is shadowing that of a third-party dependency, consider renaming the project.", + "Requirement name `{}` matches project name `{}`, but self-dependencies are not permitted without the `--dev` or `--optional` flags. If your project name (`{}`) is shadowing that of a third-party dependency, consider renaming the project.", requirement.name.cyan(), project_name.cyan(), project_name.cyan(), @@ -361,6 +365,16 @@ pub(crate) async fn add( } } + // If the user provides a single, named index, pin all requirements to that index. + let index = indexes + .first() + .as_ref() + .and_then(|index| index.name.as_ref()) + .filter(|_| indexes.len() == 1) + .inspect(|index| { + debug!("Pinning all requirements to index: `{index}`"); + }); + // Add the requirements to the `pyproject.toml` or script. let mut toml = match &target { Target::Script(script, _) => { @@ -389,6 +403,7 @@ pub(crate) async fn add( requirement, false, editable, + index.cloned(), rev.clone(), tag.clone(), branch.clone(), @@ -404,6 +419,7 @@ pub(crate) async fn add( requirement, workspace, editable, + index.cloned(), rev.clone(), tag.clone(), branch.clone(), @@ -426,7 +442,7 @@ pub(crate) async fn add( branch, marker, }) => { - let credentials = Credentials::from_url(&git); + let credentials = uv_auth::Credentials::from_url(&git); if let Some(credentials) = credentials { debug!("Caching credentials for: {git}"); GIT_STORE.insert(RepositoryUrl::new(&git), credentials); @@ -483,6 +499,13 @@ pub(crate) async fn add( }); } + // Add any indexes that were provided on the command-line. + if !raw_sources { + for index in indexes { + toml.add_index(&index)?; + } + } + let content = toml.to_string(); // Save the modified `pyproject.toml` or script. @@ -567,6 +590,7 @@ pub(crate) async fn add( &dependency_type, raw_sources, settings.as_ref(), + bounds, connectivity, concurrency, native_tls, @@ -627,6 +651,7 @@ async fn lock_and_sync( dependency_type: &DependencyType, raw_sources: bool, settings: ResolverInstallerSettingsRef<'_>, + bounds: LowerBound, connectivity: Connectivity, concurrency: Concurrency, native_tls: bool, @@ -639,6 +664,8 @@ async fn lock_and_sync( project.workspace(), venv.interpreter(), settings.into(), + bounds, + &state, Box::new(DefaultResolveLogger), connectivity, concurrency, @@ -678,7 +705,11 @@ async fn lock_and_sync( }; // Only set a minimum version for registry requirements. - if edit.source.is_some() { + if edit + .source + .as_ref() + .is_some_and(|source| !matches!(source, Source::Registry { .. })) + { continue; } @@ -730,6 +761,15 @@ async fn lock_and_sync( .with_pyproject_toml(toml::from_str(&content).map_err(ProjectError::TomlParse)?) .ok_or(ProjectError::TomlUpdate)?; + // Invalidate the project metadata. + if let VirtualProject::Project(ref project) = project { + let url = Url::from_file_path(project.project_root()) + .expect("project root is a valid URL"); + let version_id = VersionId::from_url(&url); + let existing = state.index.distributions().remove(&version_id); + debug_assert!(existing.is_some(), "distribution should exist"); + } + // If the file was modified, we have to lock again, though the only expected change is // the addition of the minimum version specifiers. lock = project::lock::do_safe_lock( @@ -738,6 +778,8 @@ async fn lock_and_sync( project.workspace(), venv.interpreter(), settings.into(), + bounds, + &state, Box::new(SummaryResolveLogger), connectivity, concurrency, @@ -783,7 +825,6 @@ async fn lock_and_sync( InstallOptions::default(), Modifications::Sufficient, settings.into(), - &state, Box::new(DefaultInstallLogger), connectivity, concurrency, @@ -869,6 +910,7 @@ fn resolve_requirement( requirement: uv_pypi_types::Requirement, workspace: bool, editable: Option, + index: Option, rev: Option, tag: Option, branch: Option, @@ -879,6 +921,7 @@ fn resolve_requirement( requirement.source.clone(), workspace, editable, + index, rev, tag, branch, diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 0b0f92455..4cbccb67e 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -9,7 +9,7 @@ use uv_cache::Cache; use uv_client::Connectivity; use uv_configuration::{ Concurrency, DevMode, DevSpecification, EditableMode, ExportFormat, ExtrasSpecification, - InstallOptions, + InstallOptions, LowerBound, }; use uv_normalize::{PackageName, DEV_DEPENDENCIES}; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; @@ -19,7 +19,7 @@ use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace} use crate::commands::pip::loggers::DefaultResolveLogger; use crate::commands::project::lock::do_safe_lock; use crate::commands::project::{ProjectError, ProjectInterpreter}; -use crate::commands::{diagnostics, pip, ExitStatus, OutputWriter}; +use crate::commands::{diagnostics, pip, ExitStatus, OutputWriter, SharedState}; use crate::printer::Printer; use crate::settings::ResolverSettings; @@ -37,6 +37,7 @@ pub(crate) async fn export( editable: EditableMode, locked: bool, frozen: bool, + include_header: bool, python: Option, settings: ResolverSettings, python_preference: PythonPreference, @@ -87,6 +88,9 @@ pub(crate) async fn export( .await? .into_interpreter(); + // Initialize any shared state. + let state = SharedState::default(); + // Lock the project. let lock = match do_safe_lock( locked, @@ -94,6 +98,8 @@ pub(crate) async fn export( project.workspace(), &interpreter, settings.as_ref(), + LowerBound::Warn, + &state, Box::new(DefaultResolveLogger), connectivity, concurrency, @@ -147,12 +153,15 @@ pub(crate) async fn export( hashes, &install_options, )?; - writeln!( - writer, - "{}", - "# This file was autogenerated by uv via the following command:".green() - )?; - writeln!(writer, "{}", format!("# {}", cmd()).green())?; + + if include_header { + writeln!( + writer, + "{}", + "# This file was autogenerated by uv via the following command:".green() + )?; + writeln!(writer, "{}", format!("# {}", cmd()).green())?; + } write!(writer, "{export}")?; } } diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 79a4a8ffc..4814609a9 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -1,14 +1,17 @@ use std::fmt::Write; use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; use anyhow::{anyhow, Context, Result}; use owo_colors::OwoColorize; use tracing::{debug, warn}; use uv_cache::Cache; +use uv_cli::AuthorFrom; use uv_client::{BaseClientBuilder, Connectivity}; -use uv_configuration::{VersionControlError, VersionControlSystem}; +use uv_configuration::{ProjectBuildBackend, VersionControlError, VersionControlSystem}; use uv_fs::{Simplified, CWD}; +use uv_git::GIT; use uv_pep440::Version; use uv_pep508::PackageName; use uv_python::{ @@ -35,7 +38,9 @@ pub(crate) async fn init( package: bool, init_kind: InitKind, vcs: Option, + build_backend: Option, no_readme: bool, + author_from: Option, no_pin_python: bool, python: Option, no_workspace: bool, @@ -62,6 +67,7 @@ pub(crate) async fn init( printer, no_workspace, no_readme, + author_from, no_pin_python, package, native_tls, @@ -110,7 +116,9 @@ pub(crate) async fn init( package, project_kind, vcs, + build_backend, no_readme, + author_from, no_pin_python, python, no_workspace, @@ -165,15 +173,19 @@ async fn init_script( printer: Printer, no_workspace: bool, no_readme: bool, + author_from: Option, no_pin_python: bool, package: bool, native_tls: bool, ) -> Result<()> { if no_workspace { - warn_user_once!("`--no_workspace` is a no-op for Python scripts, which are standalone"); + warn_user_once!("`--no-workspace` is a no-op for Python scripts, which are standalone"); } if no_readme { - warn_user_once!("`--no_readme` is a no-op for Python scripts, which are standalone"); + warn_user_once!("`--no-readme` is a no-op for Python scripts, which are standalone"); + } + if author_from.is_some() { + warn_user_once!("`--author-from` is a no-op for Python scripts, which are standalone"); } if package { warn_user_once!("`--package` is a no-op for Python scripts, which are standalone"); @@ -236,7 +248,9 @@ async fn init_project( package: bool, project_kind: InitProjectKind, vcs: Option, + build_backend: Option, no_readme: bool, + author_from: Option, no_pin_python: bool, python: Option, no_workspace: bool, @@ -475,6 +489,8 @@ async fn init_project( &requires_python, python_request.as_ref(), vcs, + build_backend, + author_from, no_readme, package, ) @@ -564,6 +580,8 @@ impl InitProjectKind { requires_python: &RequiresPython, python_request: Option<&PythonRequest>, vcs: Option, + build_backend: Option, + author_from: Option, no_readme: bool, package: bool, ) -> Result<()> { @@ -575,6 +593,8 @@ impl InitProjectKind { requires_python, python_request, vcs, + build_backend, + author_from, no_readme, package, ) @@ -587,6 +607,8 @@ impl InitProjectKind { requires_python, python_request, vcs, + build_backend, + author_from, no_readme, package, ) @@ -603,11 +625,25 @@ impl InitProjectKind { requires_python: &RequiresPython, python_request: Option<&PythonRequest>, vcs: Option, + build_backend: Option, + author_from: Option, no_readme: bool, package: bool, ) -> Result<()> { + fs_err::create_dir_all(path)?; + + // Do no fill in `authors` for non-packaged applications unless explicitly requested. + let author_from = author_from.unwrap_or_else(|| { + if package { + AuthorFrom::default() + } else { + AuthorFrom::None + } + }); + let author = get_author_info(path, author_from); + // Create the `pyproject.toml` - let mut pyproject = pyproject_project(name, requires_python, no_readme); + let mut pyproject = pyproject_project(name, requires_python, author.as_ref(), no_readme); // Include additional project configuration for packaged applications if package { @@ -616,27 +652,13 @@ impl InitProjectKind { pyproject.push_str(&pyproject_project_scripts(name, name.as_str(), "main")); // Add a build system + let build_backend = build_backend.unwrap_or_default(); pyproject.push('\n'); - pyproject.push_str(pyproject_build_system()); - } + pyproject.push_str(&pyproject_build_system(name, build_backend)); + pyproject_build_backend_prerequisites(name, path, build_backend)?; - fs_err::create_dir_all(path)?; - - // Create the source structure. - if package { - // Create `src/{name}/__init__.py`, if it doesn't exist already. - let src_dir = path.join("src").join(&*name.as_dist_info_name()); - let init_py = src_dir.join("__init__.py"); - if !init_py.try_exists()? { - fs_err::create_dir_all(&src_dir)?; - fs_err::write( - init_py, - indoc::formatdoc! {r#" - def main() -> None: - print("Hello from {name}!") - "#}, - )?; - } + // Generate `src` files + generate_package_scripts(name, path, build_backend, false)?; } else { // Create `hello.py` if it doesn't exist // TODO(zanieb): Only create `hello.py` if there are no other Python files? @@ -684,6 +706,8 @@ impl InitProjectKind { requires_python: &RequiresPython, python_request: Option<&PythonRequest>, vcs: Option, + build_backend: Option, + author_from: Option, no_readme: bool, package: bool, ) -> Result<()> { @@ -691,36 +715,23 @@ impl InitProjectKind { return Err(anyhow!("Library projects must be packaged")); } + fs_err::create_dir_all(path)?; + + let author = get_author_info(path, author_from.unwrap_or_default()); + // Create the `pyproject.toml` - let mut pyproject = pyproject_project(name, requires_python, no_readme); + let mut pyproject = pyproject_project(name, requires_python, author.as_ref(), no_readme); // Always include a build system if the project is packaged. + let build_backend = build_backend.unwrap_or_default(); pyproject.push('\n'); - pyproject.push_str(pyproject_build_system()); + pyproject.push_str(&pyproject_build_system(name, build_backend)); + pyproject_build_backend_prerequisites(name, path, build_backend)?; - fs_err::create_dir_all(path)?; fs_err::write(path.join("pyproject.toml"), pyproject)?; - // Create `src/{name}/__init__.py`, if it doesn't exist already. - let src_dir = path.join("src").join(&*name.as_dist_info_name()); - fs_err::create_dir_all(&src_dir)?; - - let init_py = src_dir.join("__init__.py"); - if !init_py.try_exists()? { - fs_err::write( - init_py, - indoc::formatdoc! {r#" - def hello() -> str: - return "Hello from {name}!" - "#}, - )?; - } - - // Create a `py.typed` file - let py_typed = src_dir.join("py.typed"); - if !py_typed.try_exists()? { - fs_err::write(py_typed, "")?; - } + // Generate `src` files + generate_package_scripts(name, path, build_backend, true)?; // Write .python-version if it doesn't exist. if let Some(python_request) = python_request { @@ -742,32 +753,98 @@ impl InitProjectKind { } } +#[derive(Debug)] +enum Author { + Name(String), + Email(String), + NameEmail { name: String, email: String }, +} + +impl Author { + fn to_toml_string(&self) -> String { + match self { + Self::NameEmail { name, email } => { + format!("{{ name = \"{name}\", email = \"{email}\" }}") + } + Self::Name(name) => format!("{{ name = \"{name}\" }}"), + Self::Email(email) => format!("{{ email = \"{email}\" }}"), + } + } +} + /// Generate the `[project]` section of a `pyproject.toml`. fn pyproject_project( name: &PackageName, requires_python: &RequiresPython, + author: Option<&Author>, no_readme: bool, ) -> String { indoc::formatdoc! {r#" [project] name = "{name}" version = "0.1.0" - description = "Add your description here"{readme} + description = "Add your description here"{readme}{authors} requires-python = "{requires_python}" dependencies = [] "#, readme = if no_readme { "" } else { "\nreadme = \"README.md\"" }, + authors = author.map_or_else(String::new, |author| format!("\nauthors = [\n {}\n]", author.to_toml_string())), requires_python = requires_python.specifiers(), } } /// Generate the `[build-system]` section of a `pyproject.toml`. -fn pyproject_build_system() -> &'static str { - indoc::indoc! {r#" - [build-system] - requires = ["hatchling"] - build-backend = "hatchling.build" - "#} +/// Generate the `[tool.]` section of a `pyproject.toml` where applicable. +fn pyproject_build_system(package: &PackageName, build_backend: ProjectBuildBackend) -> String { + let module_name = package.as_dist_info_name(); + match build_backend { + // Pure-python backends + ProjectBuildBackend::Hatch => indoc::indoc! {r#" + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#} + .to_string(), + ProjectBuildBackend::Flit => indoc::indoc! {r#" + [build-system] + requires = ["flit_core>=3.2,<4"] + build-backend = "flit_core.buildapi" + "#} + .to_string(), + ProjectBuildBackend::PDM => indoc::indoc! {r#" + [build-system] + requires = ["pdm-backend"] + build-backend = "pdm.backend" + "#} + .to_string(), + ProjectBuildBackend::Setuptools => indoc::indoc! {r#" + [build-system] + requires = ["setuptools>=61"] + build-backend = "setuptools.build_meta" + "#} + .to_string(), + // Binary build backends + ProjectBuildBackend::Maturin => indoc::formatdoc! {r#" + [tool.maturin] + module-name = "{module_name}._core" + python-packages = ["{module_name}"] + python-source = "src" + + [build-system] + requires = ["maturin>=1.0,<2.0"] + build-backend = "maturin" + "#}, + ProjectBuildBackend::Scikit => indoc::indoc! {r#" + [tool.scikit-build] + minimum-version = "build-system.requires" + build-dir = "build/{wheel_tag}" + + [build-system] + requires = ["scikit-build-core>=0.10", "pybind11"] + build-backend = "scikit_build_core.build" + "#} + .to_string(), + } } /// Generate the `[project.scripts]` section of a `pyproject.toml`. @@ -779,6 +856,198 @@ fn pyproject_project_scripts(package: &PackageName, executable_name: &str, targe "#} } +/// Generate additional files as needed for specific build backends. +fn pyproject_build_backend_prerequisites( + package: &PackageName, + path: &Path, + build_backend: ProjectBuildBackend, +) -> Result<()> { + let module_name = package.as_dist_info_name(); + match build_backend { + ProjectBuildBackend::Maturin => { + // Generate Cargo.toml + let build_file = path.join("Cargo.toml"); + if !build_file.try_exists()? { + fs_err::write( + build_file, + indoc::formatdoc! {r#" + [package] + name = "{module_name}" + version = "0.1.0" + edition = "2021" + + [lib] + name = "_core" + # "cdylib" is necessary to produce a shared library for Python to import from. + crate-type = ["cdylib"] + + [dependencies] + # "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so) + # "abi3-py39" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.9 + pyo3 = {{ version = "0.22.4", features = ["extension-module", "abi3-py39"] }} + "#}, + )?; + } + } + ProjectBuildBackend::Scikit => { + // Generate CMakeLists.txt + let build_file = path.join("CMakeLists.txt"); + if !build_file.try_exists()? { + fs_err::write( + build_file, + indoc::formatdoc! {r#" + cmake_minimum_required(VERSION 3.15) + project(${{SKBUILD_PROJECT_NAME}} LANGUAGES CXX) + + set(PYBIND11_FINDPYTHON ON) + find_package(pybind11 CONFIG REQUIRED) + + pybind11_add_module(_core MODULE src/main.cpp) + install(TARGETS _core DESTINATION ${{SKBUILD_PROJECT_NAME}}) + "#}, + )?; + } + } + _ => {} + } + Ok(()) +} + +/// Generate startup scripts for a package-based application or library. +fn generate_package_scripts( + package: &PackageName, + path: &Path, + build_backend: ProjectBuildBackend, + is_lib: bool, +) -> Result<()> { + let module_name = package.as_dist_info_name(); + + let src_dir = path.join("src"); + let pkg_dir = src_dir.join(&*module_name); + fs_err::create_dir_all(&pkg_dir)?; + + // Python script for pure-python packaged apps or libs + let pure_python_script = if is_lib { + indoc::formatdoc! {r#" + def hello() -> str: + return "Hello from {package}!" + "#} + } else { + indoc::formatdoc! {r#" + def main() -> None: + print("Hello from {package}!") + "#} + }; + + // Python script for binary-based packaged apps or libs + let binary_call_script = if is_lib { + indoc::formatdoc! {r#" + from {module_name}._core import hello_from_bin + + def hello() -> str: + return hello_from_bin() + "#} + } else { + indoc::formatdoc! {r#" + from {module_name}._core import hello_from_bin + + def main() -> None: + print(hello_from_bin()) + "#} + }; + + // .pyi file for binary script + let pyi_contents = indoc::indoc! {r" + from __future__ import annotations + + def hello_from_bin() -> str: ... + "}; + + let package_script = match build_backend { + ProjectBuildBackend::Maturin => { + // Generate lib.rs + let native_src = src_dir.join("lib.rs"); + if !native_src.try_exists()? { + fs_err::write( + native_src, + indoc::formatdoc! {r#" + use pyo3::prelude::*; + + #[pyfunction] + fn hello_from_bin() -> String {{ + return "Hello from {package}!".to_string(); + }} + + /// A Python module implemented in Rust. The name of this function must match + /// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to + /// import the module. + #[pymodule] + fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {{ + m.add_function(wrap_pyfunction!(hello_from_bin, m)?)?; + Ok(()) + }} + "#}, + )?; + } + // Generate .pyi file + let pyi_file = pkg_dir.join("_core.pyi"); + if !pyi_file.try_exists()? { + fs_err::write(pyi_file, pyi_contents)?; + }; + // Return python script calling binary + binary_call_script + } + ProjectBuildBackend::Scikit => { + // Generate main.cpp + let native_src = src_dir.join("main.cpp"); + if !native_src.try_exists()? { + fs_err::write( + native_src, + indoc::formatdoc! {r#" + #include + + std::string hello_from_bin() {{ return "Hello from {package}!"; }} + + namespace py = pybind11; + + PYBIND11_MODULE(_core, m) {{ + m.doc() = "pybind11 hello module"; + + m.def("hello_from_bin", &hello_from_bin, R"pbdoc( + A function that returns a Hello string. + )pbdoc"); + }} + "#}, + )?; + } + // Generate .pyi file + let pyi_file = pkg_dir.join("_core.pyi"); + if !pyi_file.try_exists()? { + fs_err::write(pyi_file, pyi_contents)?; + }; + // Return python script calling binary + binary_call_script + } + _ => pure_python_script, + }; + + // Create `src/{name}/__init__.py`, if it doesn't exist already. + let init_py = pkg_dir.join("__init__.py"); + if !init_py.try_exists()? { + fs_err::write(init_py, package_script)?; + } + + // Create `src/{name}/py.typed`, if it doesn't exist already. + if is_lib { + let py_typed = pkg_dir.join("py.typed"); + if !py_typed.try_exists()? { + fs_err::write(py_typed, "")?; + } + } + + Ok(()) +} + /// Initialize the version control system at the given path. fn init_vcs(path: &Path, vcs: Option) -> Result<()> { // Detect any existing version control system. @@ -826,3 +1095,63 @@ fn init_vcs(path: &Path, vcs: Option) -> Result<()> { Ok(()) } + +/// Try to get the author information. +/// +/// Currently, this only tries to get the author information from git. +fn get_author_info(path: &Path, author_from: AuthorFrom) -> Option { + if matches!(author_from, AuthorFrom::None) { + return None; + } + if matches!(author_from, AuthorFrom::Auto | AuthorFrom::Git) { + match get_author_from_git(path) { + Ok(author) => return Some(author), + Err(err) => warn!("Failed to get author from git: {err}"), + } + } + + None +} + +/// Fetch the default author from git configuration. +fn get_author_from_git(path: &Path) -> Result { + let Ok(git) = GIT.as_ref() else { + anyhow::bail!("`git` not found in PATH") + }; + + let mut name = None; + let mut email = None; + + let output = Command::new(git) + .arg("config") + .arg("--get") + .arg("user.name") + .current_dir(path) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output()?; + if output.status.success() { + name = Some(String::from_utf8_lossy(&output.stdout).trim().to_string()); + } + + let output = Command::new(git) + .arg("config") + .arg("--get") + .arg("user.email") + .current_dir(path) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output()?; + if output.status.success() { + email = Some(String::from_utf8_lossy(&output.stdout).trim().to_string()); + } + + let author = match (name, email) { + (Some(name), Some(email)) => Author::NameEmail { name, email }, + (Some(name), None) => Author::Name(name), + (None, Some(email)) => Author::Email(email), + (None, None) => anyhow::bail!("No author information found"), + }; + + Ok(author) +} diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 1e4dcc480..c80bd6726 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -1,23 +1,23 @@ #![allow(clippy::single_match_else)] +use std::collections::BTreeSet; +use std::fmt::Write; +use std::path::Path; + use anstream::eprint; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashMap}; -use std::collections::BTreeSet; -use std::fmt::Write; -use std::path::Path; use tracing::debug; -use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - BuildOptions, Concurrency, Constraints, ExtrasSpecification, Reinstall, Upgrade, + BuildOptions, Concurrency, Constraints, ExtrasSpecification, LowerBound, Reinstall, Upgrade, }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; use uv_distribution_types::{ - DependencyMetadata, IndexLocations, NameRequirementSpecification, + DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, UnresolvedRequirementSpecification, }; use uv_git::ResolvedRepositoryReference; @@ -26,9 +26,10 @@ use uv_pep440::Version; use uv_pypi_types::{Requirement, SupportedEnvironments}; use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements}; +use uv_requirements::ExtrasResolver; use uv_resolver::{ - FlatIndex, Lock, Options, OptionsBuilder, PythonRequirement, RequiresPython, ResolverManifest, - ResolverMarkers, SatisfiesResult, + FlatIndex, InMemoryIndex, Lock, Options, OptionsBuilder, PythonRequirement, RequiresPython, + ResolverManifest, ResolverMarkers, SatisfiesResult, }; use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_warnings::{warn_user, warn_user_once}; @@ -38,6 +39,7 @@ use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, Summary use crate::commands::project::{ find_requires_python, ProjectError, ProjectInterpreter, SharedState, }; +use crate::commands::reporters::ResolverReporter; use crate::commands::{diagnostics, pip, ExitStatus}; use crate::printer::Printer; use crate::settings::{ResolverSettings, ResolverSettingsRef}; @@ -99,6 +101,9 @@ pub(crate) async fn lock( .await? .into_interpreter(); + // Initialize any shared state. + let state = SharedState::default(); + // Perform the lock operation. match do_safe_lock( locked, @@ -106,6 +111,8 @@ pub(crate) async fn lock( &workspace, &interpreter, settings.as_ref(), + LowerBound::Warn, + &state, Box::new(DefaultResolveLogger), connectivity, concurrency, @@ -151,6 +158,8 @@ pub(super) async fn do_safe_lock( workspace: &Workspace, interpreter: &Interpreter, settings: ResolverSettingsRef<'_>, + bounds: LowerBound, + state: &SharedState, logger: Box, connectivity: Connectivity, concurrency: Concurrency, @@ -158,17 +167,6 @@ pub(super) async fn do_safe_lock( cache: &Cache, printer: Printer, ) -> Result { - // Use isolate state for universal resolution. When resolving, we don't enforce that the - // prioritized distributions match the current platform. So if we lock here, then try to - // install from the same state, and we end up performing a resolution during the sync (i.e., - // for the build dependencies of a source distribution), we may try to use incompatible - // distributions. - // TODO(charlie): In universal resolution, we should still track version compatibility! We - // just need to accept versions that are platform-incompatible. That would also make us more - // likely to (e.g.) download a wheel that we'll end up using when installing. This would - // make it safe to share the state. - let state = SharedState::default(); - if frozen { // Read the existing lockfile, but don't attempt to lock the project. let existing = read(workspace) @@ -187,7 +185,8 @@ pub(super) async fn do_safe_lock( interpreter, Some(existing), settings, - &state, + bounds, + state, logger, connectivity, concurrency, @@ -213,7 +212,8 @@ pub(super) async fn do_safe_lock( interpreter, existing, settings, - &state, + bounds, + state, logger, connectivity, concurrency, @@ -238,6 +238,7 @@ async fn do_lock( interpreter: &Interpreter, existing_lock: Option, settings: ResolverSettingsRef<'_>, + bounds: LowerBound, state: &SharedState, logger: Box, connectivity: Connectivity, @@ -333,7 +334,9 @@ async fn do_lock( if requires_python.is_unbounded() { let default = RequiresPython::greater_than_equal_version(&interpreter.python_minor_version()); - warn_user_once!("The workspace `requires-python` value does not contain a lower bound: `{requires_python}`. Set a lower bound to indicate the minimum compatible Python version (e.g., `{default}`)."); + warn_user_once!("The workspace `requires-python` value (`{requires_python}`) does not contain a lower bound. Add a lower bound to indicate the minimum compatible Python version (e.g., `{default}`)."); + } else if requires_python.is_exact_without_patch() { + warn_user_once!("The workspace `requires-python` value (`{requires_python}`) contains an exact match without a patch version. When omitted, the patch version is implicitly `0` (e.g., `{requires_python}.0`). Did you mean `{requires_python}.*`?"); } requires_python } else { @@ -368,8 +371,10 @@ async fn do_lock( PythonRequirement::from_requires_python(interpreter, requires_python.clone()); // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -413,7 +418,9 @@ async fn do_lock( // 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?; + let entries = client + .fetch(index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, None, &hasher, build_options) }; @@ -437,6 +444,7 @@ async fn do_lock( build_options, &build_hasher, exclude_newer, + bounds, sources, concurrency, ); @@ -460,12 +468,19 @@ async fn do_lock( build_options, upgrade, &options, + &hasher, + &state.index, &database, printer, ) .await { Ok(result) => Some(result), + Err(ProjectError::Lock(err)) if err.is_resolution() => { + // Resolver errors are not recoverable, as such errors can leave the resolver in a + // broken state. Specifically, tasks that fail with an error can be left as pending. + return Err(ProjectError::Lock(err)); + } Err(err) => { warn_user!("Failed to validate existing lockfile: {err}"); None @@ -487,8 +502,6 @@ async fn do_lock( // The lockfile did not contain enough information to obtain a resolution, fallback // to a fresh resolve. _ => { - debug!("Starting clean resolution"); - // Determine whether we can reuse the existing package versions. let versions_lock = existing_lock.as_ref().and_then(|lock| match &lock { ValidatedLock::Satisfies(lock) => Some(lock), @@ -534,8 +547,11 @@ async fn do_lock( // Resolve the requirements. let resolution = pip::operations::resolve( - workspace - .members_requirements() + ExtrasResolver::new(&hasher, &state.index, database) + .with_reporter(ResolverReporter::from(printer)) + .resolve(workspace.members_requirements()) + .await? + .into_iter() .chain(requirements.iter().cloned()) .map(UnresolvedRequirementSpecification::from) .collect(), @@ -635,6 +651,8 @@ impl ValidatedLock { build_options: &BuildOptions, upgrade: &Upgrade, options: &Options, + hasher: &HashStrategy, + index: &InMemoryIndex, database: &DistributionDatabase<'_, Context>, printer: Printer, ) -> Result { @@ -759,6 +777,8 @@ impl ValidatedLock { indexes, build_options, interpreter.tags()?, + hasher, + index, database, ) .await? @@ -842,12 +862,6 @@ impl ValidatedLock { ); Ok(Self::Preferable(lock)) } - SatisfiesResult::MissingMetadata(name, version) => { - debug!( - "Ignoring existing lockfile due to missing metadata for: `{name}=={version}`" - ); - Ok(Self::Preferable(lock)) - } SatisfiesResult::MismatchedRequiresDist(name, version, expected, actual) => { debug!( "Ignoring existing lockfile due to mismatched `requires-dist` for: `{name}=={version}`\n Expected: {:?}\n Actual: {:?}", diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 699802b92..ed6fd4a01 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -5,14 +5,15 @@ use itertools::Itertools; use owo_colors::OwoColorize; use tracing::debug; -use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; -use uv_configuration::{Concurrency, Constraints, ExtrasSpecification, Reinstall, Upgrade}; +use uv_configuration::{ + Concurrency, Constraints, ExtrasSpecification, LowerBound, Reinstall, Upgrade, +}; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; use uv_distribution_types::{ - Resolution, UnresolvedRequirement, UnresolvedRequirementSpecification, + Index, Resolution, UnresolvedRequirement, UnresolvedRequirementSpecification, }; use uv_fs::Simplified; use uv_git::ResolvedRepositoryReference; @@ -27,9 +28,7 @@ use uv_python::{ VersionRequest, }; use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements}; -use uv_requirements::{ - NamedRequirementsError, NamedRequirementsResolver, RequirementsSpecification, -}; +use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification}; use uv_resolver::{ FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython, ResolutionGraph, ResolverMarkers, @@ -169,7 +168,7 @@ pub(crate) enum ProjectError { Name(#[from] uv_normalize::InvalidNameError), #[error(transparent)] - NamedRequirements(#[from] uv_requirements::NamedRequirementsError), + Requirements(#[from] uv_requirements::Error), #[error(transparent)] PyprojectMut(#[from] uv_workspace::pyproject_mut::Error), @@ -525,24 +524,40 @@ pub(crate) async fn get_or_init_environment( let venv = workspace.venv(); // Avoid removing things that are not virtual environments - if venv.exists() && !venv.join("pyvenv.cfg").exists() { - return Err(ProjectError::InvalidProjectEnvironmentDir( - venv, - "it is not a compatible environment but cannot be recreated because it is not a virtual environment".to_string(), - )); - } + let should_remove = match (venv.try_exists(), venv.join("pyvenv.cfg").try_exists()) { + // It's a virtual environment we can remove it + (_, Ok(true)) => true, + // It doesn't exist at all, we should use it without deleting it to avoid TOCTOU bugs + (Ok(false), Ok(false)) => false, + // If it's not a virtual environment, bail + (Ok(true), Ok(false)) => { + return Err(ProjectError::InvalidProjectEnvironmentDir( + venv, + "it is not a compatible environment but cannot be recreated because it is not a virtual environment".to_string(), + )); + } + // Similarly, if we can't _tell_ if it exists we should bail + (_, Err(err)) | (Err(err), _) => { + return Err(ProjectError::InvalidProjectEnvironmentDir( + venv, + format!("it is not a compatible environment but cannot be recreated because uv cannot determine if it is a virtual environment: {err}"), + )); + } + }; // Remove the existing virtual environment if it doesn't meet the requirements. - match fs_err::remove_dir_all(&venv) { - Ok(()) => { - writeln!( - printer.stderr(), - "Removed virtual environment at: {}", - venv.user_display().cyan() - )?; + if should_remove { + match fs_err::remove_dir_all(&venv) { + Ok(()) => { + writeln!( + printer.stderr(), + "Removed virtual environment at: {}", + venv.user_display().cyan() + )?; + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} + Err(e) => return Err(e.into()), } - Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => return Err(e.into()), } writeln!( @@ -594,7 +609,7 @@ pub(crate) async fn resolve_names( native_tls: bool, cache: &Cache, printer: Printer, -) -> Result, NamedRequirementsError> { +) -> Result, uv_requirements::Error> { // Partition the requirements into named and unnamed requirements. let (mut requirements, unnamed): (Vec<_>, Vec<_>) = requirements @@ -633,8 +648,10 @@ pub(crate) async fn resolve_names( } = settings; // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -688,6 +705,7 @@ pub(crate) async fn resolve_names( build_options, &build_hasher, *exclude_newer, + LowerBound::Allow, *sources, concurrency, ); @@ -695,13 +713,12 @@ pub(crate) async fn resolve_names( // Resolve the unnamed requirements. requirements.extend( NamedRequirementsResolver::new( - unnamed, &hasher, &state.index, DistributionDatabase::new(&client, &build_dispatch, concurrency.downloads), ) .with_reporter(ResolverReporter::from(printer)) - .resolve() + .resolve(unnamed.into_iter()) .await?, ); @@ -781,8 +798,10 @@ pub(crate) async fn resolve_environment<'a>( let python_requirement = PythonRequirement::from_interpreter(interpreter); // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -844,7 +863,9 @@ pub(crate) async fn resolve_environment<'a>( // 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?; + let entries = client + .fetch(index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; @@ -868,6 +889,7 @@ pub(crate) async fn resolve_environment<'a>( build_options, &build_hasher, exclude_newer, + LowerBound::Allow, sources, concurrency, ); @@ -939,8 +961,10 @@ pub(crate) async fn sync_environment( let tags = venv.interpreter().tags()?; // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -974,7 +998,9 @@ pub(crate) async fn sync_environment( // 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?; + let entries = client + .fetch(index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; @@ -998,6 +1024,7 @@ pub(crate) async fn sync_environment( build_options, &build_hasher, exclude_newer, + LowerBound::Allow, sources, concurrency, ); @@ -1127,8 +1154,10 @@ pub(crate) async fn update_environment( } // Add all authenticated sources to the cache. - for url in index_locations.urls() { - store_credentials_from_url(url); + for index in index_locations.allowed_indexes() { + if let Some(credentials) = index.credentials() { + uv_auth::store_credentials(index.raw_url(), credentials); + } } // Initialize the registry client. @@ -1176,7 +1205,9 @@ pub(crate) async fn update_environment( // 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?; + let entries = client + .fetch(index_locations.flat_indexes().map(Index::url)) + .await?; FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; @@ -1200,6 +1231,7 @@ pub(crate) async fn update_environment( build_options, &build_hasher, *exclude_newer, + LowerBound::Allow, *sources, concurrency, ); @@ -1336,7 +1368,7 @@ fn warn_on_requirements_txt_setting( warn_user_once!("Ignoring `--no-index` from requirements file. Instead, use the `--no-index` command-line argument, or set `no-index` in a `uv.toml` or `pyproject.toml` file."); } else { if let Some(index_url) = index_url { - if settings.index_locations.index() != Some(index_url) { + if settings.index_locations.default_index().map(Index::url) != Some(index_url) { warn_user_once!( "Ignoring `--index-url` from requirements file: `{index_url}`. Instead, use the `--index-url` command-line argument, or set `index-url` in a `uv.toml` or `pyproject.toml` file." ); @@ -1345,8 +1377,8 @@ fn warn_on_requirements_txt_setting( for extra_index_url in extra_index_urls { if !settings .index_locations - .extra_index() - .contains(extra_index_url) + .implicit_indexes() + .any(|index| index.url() == extra_index_url) { warn_user_once!( "Ignoring `--extra-index-url` from requirements file: `{extra_index_url}`. Instead, use the `--extra-index-url` command-line argument, or set `extra-index-url` in a `uv.toml` or `pyproject.toml` file.`" @@ -1355,7 +1387,11 @@ fn warn_on_requirements_txt_setting( } } for find_link in find_links { - if !settings.index_locations.flat_index().contains(find_link) { + if !settings + .index_locations + .flat_indexes() + .any(|index| index.url() == find_link) + { warn_user_once!( "Ignoring `--find-links` from requirements file: `{find_link}`. Instead, use the `--find-links` command-line argument, or set `find-links` in a `uv.toml` or `pyproject.toml` file.`" ); diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index fd0155c77..d5671388e 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -5,7 +5,9 @@ use std::path::Path; use owo_colors::OwoColorize; use uv_cache::Cache; use uv_client::Connectivity; -use uv_configuration::{Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions}; +use uv_configuration::{ + Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions, LowerBound, +}; use uv_fs::Simplified; use uv_pep508::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; @@ -61,7 +63,7 @@ pub(crate) async fn remove( } if no_sync { warn_user_once!( - "`--no_sync` is a no-op for Python scripts with inline metadata, which always run in isolation" + "`--no-sync` is a no-op for Python scripts with inline metadata, which always run in isolation" ); } Target::Script(script) @@ -166,6 +168,9 @@ pub(crate) async fn remove( ) .await?; + // Initialize any shared state. + let state = SharedState::default(); + // Lock and sync the environment, if necessary. let lock = project::lock::do_safe_lock( locked, @@ -173,6 +178,8 @@ pub(crate) async fn remove( project.workspace(), venv.interpreter(), settings.as_ref().into(), + LowerBound::Allow, + &state, Box::new(DefaultResolveLogger), connectivity, concurrency, @@ -193,9 +200,6 @@ pub(crate) async fn remove( let extras = ExtrasSpecification::All; let install_options = InstallOptions::default(); - // Initialize any shared state. - let state = SharedState::default(); - project::sync::do_sync( InstallTarget::from(&project), &venv, @@ -206,7 +210,6 @@ pub(crate) async fn remove( install_options, Modifications::Exact, settings.as_ref().into(), - &state, Box::new(DefaultInstallLogger), connectivity, concurrency, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 5d7a839ad..38894f4c5 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -7,27 +7,33 @@ use std::path::{Path, PathBuf}; use anstream::eprint; use anyhow::{anyhow, bail, Context}; +use futures::StreamExt; use itertools::Itertools; use owo_colors::OwoColorize; use tokio::process::Command; use tracing::{debug, warn}; +use url::Url; use uv_cache::Cache; use uv_cli::ExternalCommand; use uv_client::{BaseClientBuilder, Connectivity}; use uv_configuration::{ - Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions, SourceStrategy, + Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions, LowerBound, + SourceStrategy, }; use uv_distribution::LoweredRequirement; +use uv_fs::which::is_executable; use uv_fs::{PythonExt, Simplified}; use uv_installer::{SatisfiesResult, SitePackages}; use uv_normalize::PackageName; + use uv_python::{ EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVariant, PythonVersionFile, VersionRequest, }; use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_resolver::Lock; -use uv_scripts::Pep723Script; +use uv_scripts::Pep723Item; +use uv_static::EnvVars; use uv_warnings::warn_user; use uv_workspace::{DiscoveryOptions, InstallTarget, VirtualProject, Workspace, WorkspaceError}; @@ -50,8 +56,8 @@ use crate::settings::ResolverInstallerSettings; #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn run( project_dir: &Path, - script: Option, - command: RunCommand, + script: Option, + command: Option, requirements: Vec, show_resolution: bool, locked: bool, @@ -105,11 +111,29 @@ pub(crate) async fn run( // Determine whether the command to execute is a PEP 723 script. let temp_dir; let script_interpreter = if let Some(script) = script { - writeln!( - printer.stderr(), - "Reading inline script metadata from: {}", - script.path.user_display().cyan() - )?; + match &script { + Pep723Item::Script(script) => { + writeln!( + printer.stderr(), + "Reading inline script metadata from `{}`", + script.path.user_display().cyan() + )?; + } + Pep723Item::Stdin(_) => { + writeln!( + printer.stderr(), + "Reading inline script metadata from `{}`", + "stdin".cyan() + )?; + } + Pep723Item::Remote(_) => { + writeln!( + printer.stderr(), + "Reading inline script metadata from {}", + "remote URL".cyan() + )?; + } + } let (source, python_request) = if let Some(request) = python.as_deref() { // (1) Explicit request from user @@ -124,7 +148,7 @@ pub(crate) async fn run( } else { // (3) `Requires-Python` in the script let request = script - .metadata + .metadata() .requires_python .as_ref() .map(|requires_python| { @@ -153,7 +177,7 @@ pub(crate) async fn run( .await? .into_interpreter(); - if let Some(requires_python) = script.metadata.requires_python.as_ref() { + if let Some(requires_python) = script.metadata().requires_python.as_ref() { if !requires_python.contains(interpreter.python_version()) { let err = match source { PythonRequestSource::UserRequest => { @@ -180,13 +204,34 @@ pub(crate) async fn run( } } + // Determine the working directory for the script. + let script_dir = match &script { + Pep723Item::Script(script) => std::path::absolute(&script.path)? + .parent() + .expect("script path has no parent") + .to_owned(), + Pep723Item::Stdin(..) | Pep723Item::Remote(..) => std::env::current_dir()?, + }; + let script = script.into_metadata(); + // Install the script requirements, if necessary. Otherwise, use an isolated environment. - if let Some(dependencies) = script.metadata.dependencies { - // // Collect any `tool.uv.sources` from the script. + if let Some(dependencies) = script.dependencies { + // Collect any `tool.uv.index` from the script. + let empty = Vec::default(); + let script_indexes = match settings.sources { + SourceStrategy::Enabled => script + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.indexes.as_deref()) + .unwrap_or(&empty), + SourceStrategy::Disabled => &empty, + }; + + // Collect any `tool.uv.sources` from the script. let empty = BTreeMap::default(); let script_sources = match settings.sources { SourceStrategy::Enabled => script - .metadata .tool .as_ref() .and_then(|tool| tool.uv.as_ref()) @@ -194,16 +239,17 @@ pub(crate) async fn run( .unwrap_or(&empty), SourceStrategy::Disabled => &empty, }; - let script_path = std::path::absolute(script.path)?; - let script_dir = script_path.parent().expect("script path has no parent"); let requirements = dependencies .into_iter() .flat_map(|requirement| { LoweredRequirement::from_non_workspace_requirement( requirement, - script_dir, + script_dir.as_ref(), script_sources, + script_indexes, + &settings.index_locations, + LowerBound::Allow, ) .map_ok(LoweredRequirement::into_inner) }) @@ -500,6 +546,8 @@ pub(crate) async fn run( project.workspace(), venv.interpreter(), settings.as_ref().into(), + LowerBound::Allow, + &state, if show_resolution { Box::new(DefaultResolveLogger) } else { @@ -547,7 +595,6 @@ pub(crate) async fn run( install_options, Modifications::Sufficient, settings.as_ref().into(), - &state, if show_resolution { Box::new(DefaultInstallLogger) } else { @@ -708,7 +755,7 @@ pub(crate) async fn run( diagnostics::build(dist, err); return Ok(ExitStatus::Failure); } - Err(ProjectError::Operation(operations::Error::Named(err))) => { + Err(ProjectError::Operation(operations::Error::Requirements(err))) => { let err = miette::Report::msg(format!("{err}")) .context("Invalid `--with` requirement"); eprint!("{err:?}"); @@ -751,6 +798,73 @@ pub(crate) async fn run( .as_ref() .map_or_else(|| &base_interpreter, |env| env.interpreter()); + // Check if any run command is given. + // If not, print the available scripts for the current interpreter. + let Some(command) = command else { + writeln!( + printer.stdout(), + "Provide a command or script to invoke with `uv run ` or `uv run