Implement uv pip index versions command
#35067
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: {} | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: true | |
| jobs: | |
| plan: | |
| runs-on: depot-ubuntu-24.04 | |
| outputs: | |
| test-code: ${{ steps.plan.outputs.test_code }} | |
| check-schema: ${{ steps.plan.outputs.check_schema }} | |
| build-release-binaries: ${{ steps.plan.outputs.build_release_binaries }} | |
| run-checks: ${{ steps.plan.outputs.run_checks }} | |
| test-publish: ${{ steps.plan.outputs.test_publish }} | |
| test-windows-trampoline: ${{ steps.plan.outputs.test_windows_trampoline }} | |
| save-rust-cache: ${{ steps.plan.outputs.save_rust_cache }} | |
| run-bench: ${{ steps.plan.outputs.run_bench }} | |
| test-smoke: ${{ steps.plan.outputs.test_smoke }} | |
| test-ecosystem: ${{ steps.plan.outputs.test_ecosystem }} | |
| test-integration: ${{ steps.plan.outputs.test_integration }} | |
| test-system: ${{ steps.plan.outputs.test_system }} | |
| test-macos: ${{ steps.plan.outputs.test_macos }} | |
| build-docker: ${{ steps.plan.outputs.build_docker }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: "Plan" | |
| id: plan | |
| shell: bash | |
| env: | |
| GH_REF: ${{ github.ref }} | |
| HAS_SKIP_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'test:skip') }} | |
| HAS_INTEGRATION_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'test:integration') }} | |
| HAS_SYSTEM_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'test:system') }} | |
| HAS_EXTENDED_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'test:extended') }} | |
| HAS_MACOS_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'test:macos') }} | |
| HAS_BUILD_SKIP_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'build:skip') }} | |
| HAS_BUILD_SKIP_RELEASE_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'build:skip-release') }} | |
| HAS_BUILD_RELEASE_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'build:release') }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| [[ "$GH_REF" == "refs/heads/main" ]] && on_main_branch=1 | |
| [[ "$HAS_SKIP_LABEL" == "true" ]] && has_skip_label=1 | |
| [[ "$HAS_INTEGRATION_LABEL" == "true" ]] && has_integration_label=1 | |
| [[ "$HAS_SYSTEM_LABEL" == "true" ]] && has_system_label=1 | |
| [[ "$HAS_EXTENDED_LABEL" == "true" ]] && has_extended_label=1 | |
| [[ "$HAS_MACOS_LABEL" == "true" ]] && has_macos_label=1 | |
| [[ "$HAS_BUILD_SKIP_LABEL" == "true" ]] && has_build_skip_label=1 | |
| [[ "$HAS_BUILD_SKIP_RELEASE_LABEL" == "true" ]] && has_build_skip_release_label=1 | |
| [[ "$HAS_BUILD_RELEASE_LABEL" == "true" ]] && has_build_release_label=1 | |
| # Detect changed files | |
| while IFS= read -r file; do | |
| [[ -z "$file" ]] && continue | |
| [[ "$file" =~ \.rs$ ]] && rust_code_changed=1 | |
| [[ "$file" == "Cargo.toml" || "$file" == "Cargo.lock" || "$file" =~ ^crates/.*/Cargo\.toml$ ]] && rust_deps_changed=1 | |
| [[ "$file" == "rust-toolchain.toml" || "$file" =~ ^\.cargo/ ]] && rust_config_changed=1 | |
| [[ "$file" == "pyproject.toml" || "$file" =~ ^crates/.*/pyproject\.toml$ ]] && python_config_changed=1 | |
| [[ "$file" =~ ^\.github/workflows/.*\.yml$ ]] && workflow_changed=1 | |
| [[ "$file" == ".github/workflows/build-release-binaries.yml" ]] && release_workflow_changed=1 | |
| [[ "$file" == ".github/workflows/ci.yml" ]] && ci_workflow_changed=1 | |
| [[ "$file" == "uv.schema.json" ]] && schema_changed=1 | |
| [[ "$file" =~ ^crates/uv-publish/ || "$file" =~ ^scripts/publish/ ]] && publish_code_changed=1 | |
| [[ "$file" == ".github/workflows/test-windows-trampolines.yml" ]] && trampoline_workflow_changed=1 | |
| [[ "$file" =~ ^crates/uv-trampoline/ || "$file" =~ ^crates/uv-trampoline-builder/ ]] && trampoline_code_changed=1 | |
| [[ "$file" =~ ^crates/uv-build/ ]] && uv_build_changed=1 | |
| [[ "$file" == "Dockerfile" ]] && dockerfile_changed=1 | |
| [[ "$file" == ".github/workflows/build-docker.yml" ]] && docker_workflow_changed=1 | |
| [[ "$file" == ".github/workflows/test-system.yml" ]] && system_workflow_changed=1 | |
| [[ "$file" =~ ^docs/ || "$file" =~ ^mkdocs.*\.yml$ || "$file" =~ \.md$ || "$file" =~ ^bin/ || "$file" =~ ^assets/ ]] && continue | |
| any_code_changed=1 | |
| done <<< "$(git diff --name-only "${BASE_SHA:-origin/main}...HEAD")" | |
| # Derived groups | |
| [[ $rust_code_changed || $rust_deps_changed || $rust_config_changed ]] && any_rust_changed=1 | |
| [[ $python_config_changed || $rust_deps_changed || $rust_config_changed || $uv_build_changed || $release_workflow_changed ]] && release_build_changed=1 | |
| [[ $publish_code_changed || $ci_workflow_changed ]] && publish_changed=1 | |
| [[ $rust_deps_changed || $rust_config_changed || $workflow_changed ]] && cache_relevant_changed=1 | |
| [[ $python_config_changed || $rust_deps_changed || $rust_config_changed || $dockerfile_changed || $docker_workflow_changed ]] && docker_build_changed=1 | |
| # Decisions | |
| [[ ! $has_skip_label && ($any_code_changed || $on_main_branch) ]] && test_code=1 | |
| [[ $schema_changed ]] && check_schema=1 | |
| [[ ! $has_skip_label && ! $has_build_skip_label && ! $has_build_skip_release_label && ($release_build_changed || $has_build_release_label) ]] && build_release_binaries=1 | |
| [[ ! $has_skip_label ]] && run_checks=1 | |
| [[ $publish_changed || $on_main_branch ]] && test_publish=1 | |
| [[ ! $has_skip_label && ($trampoline_code_changed || $trampoline_workflow_changed || $rust_deps_changed || $on_main_branch) ]] && test_windows_trampoline=1 | |
| [[ $on_main_branch || $cache_relevant_changed ]] && save_rust_cache=1 | |
| [[ ! $has_skip_label && ($any_rust_changed || $on_main_branch) ]] && run_bench=1 | |
| [[ ! $has_skip_label ]] && test_smoke=1 | |
| [[ ! $has_skip_label ]] && test_ecosystem=1 | |
| [[ $has_integration_label || $has_extended_label || $on_main_branch ]] && test_integration=1 | |
| [[ $has_system_label || $has_extended_label || $on_main_branch || $system_workflow_changed ]] && test_system=1 | |
| [[ $has_macos_label || $has_extended_label || $on_main_branch ]] && test_macos=1 | |
| [[ ! $has_skip_label && $docker_build_changed ]] && build_docker=1 | |
| # Output (convert 1/empty to true/false for GHA) | |
| out() { [[ "$2" ]] && echo "$1=true" || echo "$1=false"; } | |
| { | |
| out test_code "$test_code" | |
| out check_schema "$check_schema" | |
| out build_release_binaries "$build_release_binaries" | |
| out run_checks "$run_checks" | |
| out test_publish "$test_publish" | |
| out test_windows_trampoline "$test_windows_trampoline" | |
| out save_rust_cache "$save_rust_cache" | |
| out run_bench "$run_bench" | |
| out test_smoke "$test_smoke" | |
| out test_ecosystem "$test_ecosystem" | |
| out test_integration "$test_integration" | |
| out test_system "$test_system" | |
| out test_macos "$test_macos" | |
| out build_docker "$build_docker" | |
| } >> "$GITHUB_OUTPUT" | |
| check-fmt: | |
| uses: ./.github/workflows/check-fmt.yml | |
| check-lint: | |
| needs: plan | |
| uses: ./.github/workflows/check-lint.yml | |
| with: | |
| code-changed: ${{ needs.plan.outputs.test-code }} | |
| save-rust-cache: ${{ needs.plan.outputs.save-rust-cache }} | |
| check-docs: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.run-checks == 'true' }} | |
| uses: ./.github/workflows/check-docs.yml | |
| secrets: inherit | |
| check-zizmor: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.run-checks == 'true' }} | |
| uses: ./.github/workflows/check-zizmor.yml | |
| permissions: | |
| contents: read | |
| security-events: write | |
| check-publish: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.test-code == 'true' }} | |
| uses: ./.github/workflows/check-publish.yml | |
| check-release: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.run-checks == 'true' }} | |
| uses: ./.github/workflows/check-release.yml | |
| check-generated-files: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.test-code == 'true' }} | |
| uses: ./.github/workflows/check-generated-files.yml | |
| with: | |
| schema-changed: ${{ needs.plan.outputs.check-schema }} | |
| save-rust-cache: ${{ needs.plan.outputs.save-rust-cache }} | |
| test: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.test-code == 'true' }} | |
| uses: ./.github/workflows/test.yml | |
| with: | |
| save-rust-cache: ${{ needs.plan.outputs.save-rust-cache }} | |
| test-macos: ${{ needs.plan.outputs.test-macos }} | |
| test-windows-trampolines: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.test-windows-trampoline == 'true' }} | |
| uses: ./.github/workflows/test-windows-trampolines.yml | |
| build-dev-binaries: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.test-code == 'true' }} | |
| uses: ./.github/workflows/build-dev-binaries.yml | |
| with: | |
| save-rust-cache: ${{ needs.plan.outputs.save-rust-cache }} | |
| test-smoke: | |
| needs: | |
| - plan | |
| - build-dev-binaries | |
| if: ${{ needs.plan.outputs.test-smoke == 'true' }} | |
| uses: ./.github/workflows/test-smoke.yml | |
| with: | |
| sha: ${{ github.sha }} | |
| test-integration: | |
| needs: | |
| - plan | |
| - build-dev-binaries | |
| if: ${{ needs.plan.outputs.test-integration == 'true' }} | |
| uses: ./.github/workflows/test-integration.yml | |
| secrets: inherit | |
| permissions: | |
| id-token: write | |
| with: | |
| sha: ${{ github.sha }} | |
| test-system: | |
| needs: | |
| - plan | |
| - build-dev-binaries | |
| if: ${{ needs.plan.outputs.test-system == 'true' }} | |
| uses: ./.github/workflows/test-system.yml | |
| with: | |
| sha: ${{ github.sha }} | |
| test-ecosystem: | |
| needs: | |
| - plan | |
| - build-dev-binaries | |
| if: ${{ needs.plan.outputs.test-ecosystem == 'true' }} | |
| uses: ./.github/workflows/test-ecosystem.yml | |
| with: | |
| sha: ${{ github.sha }} | |
| build-release-binaries: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.build-release-binaries == 'true' }} | |
| uses: ./.github/workflows/build-release-binaries.yml | |
| secrets: inherit | |
| build-docker: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.build-docker == 'true' }} | |
| uses: ./.github/workflows/build-docker.yml | |
| secrets: inherit | |
| permissions: | |
| contents: read | |
| id-token: write | |
| packages: write | |
| attestations: write | |
| bench: | |
| needs: plan | |
| if: ${{ needs.plan.outputs.run-bench == 'true' }} | |
| uses: ./.github/workflows/bench.yml | |
| secrets: inherit | |
| with: | |
| save-rust-cache: ${{ needs.plan.outputs.save-rust-cache }} | |
| # This job cannot be moved into a reusable workflow because it includes coverage for uploading | |
| # attestations and PyPI does not support attestations in reusable workflows. | |
| test-publish: | |
| name: "test uv publish" | |
| timeout-minutes: 20 | |
| needs: | |
| - plan | |
| - build-dev-binaries | |
| runs-on: ubuntu-latest | |
| # Only the main repository is a trusted publisher | |
| if: ${{ github.repository == 'astral-sh/uv' && github.event.pull_request.head.repo.fork != true && needs.plan.outputs.test-publish == 'true' }} | |
| environment: uv-test-publish | |
| env: | |
| # No dbus in GitHub Actions | |
| PYTHON_KEYRING_BACKEND: keyrings.alt.file.PlaintextKeyring | |
| PYTHON_VERSION: 3.12 | |
| permissions: | |
| # For trusted publishing | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 | |
| with: | |
| python-version: "${{ env.PYTHON_VERSION }}" | |
| - name: "Download binary" | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| name: uv-linux-libc-${{ github.sha }} | |
| - name: "Prepare binary" | |
| run: chmod +x ./uv | |
| - name: "Build astral-test-pypa-gh-action" | |
| run: | | |
| # Build a yet unused version of `astral-test-pypa-gh-action` | |
| mkdir astral-test-pypa-gh-action | |
| cd astral-test-pypa-gh-action | |
| ../uv init --package | |
| # Get the latest patch version | |
| patch_version=$(curl https://test.pypi.org/simple/astral-test-pypa-gh-action/?format=application/vnd.pypi.simple.v1+json | jq --raw-output '.files[-1].filename' | sed 's/astral_test_pypa_gh_action-0\.1\.\([0-9]\+\)\.tar\.gz/\1/') | |
| # Set the current version to one higher (which should be unused) | |
| sed -i "s/0.1.0/0.1.$((patch_version + 1))/g" pyproject.toml | |
| ../uv build | |
| - name: "Publish astral-test-pypa-gh-action" | |
| uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 | |
| with: | |
| # With this GitHub action, we can't do as rigid checks as with our custom Python script, so we publish more | |
| # leniently | |
| skip-existing: "true" | |
| verbose: "true" | |
| repository-url: "https://test.pypi.org/legacy/" | |
| packages-dir: "astral-test-pypa-gh-action/dist" | |
| - name: "Request GitLab OIDC tokens for impersonation" | |
| uses: digital-blueprint/gitlab-pipeline-trigger-action@20e77989b24af658ba138a0aa5291bdc657f1505 # v1.3.0 | |
| with: | |
| host: gitlab.com | |
| id: astral-test-publish/astral-test-gitlab-pypi-tp | |
| ref: main | |
| trigger_token: ${{ secrets.GITLAB_TEST_PUBLISH_TRIGGER_TOKEN }} | |
| access_token: ${{ secrets.GITLAB_TEST_PUBLISH_ACCESS_TOKEN }} | |
| download_artifacts: true | |
| fail_if_no_artifacts: true | |
| download_path: ./gitlab-artifacts | |
| - name: "Load GitLab OIDC tokens from GitLab job artifacts" | |
| id: load-gitlab-oidc-token | |
| run: | | |
| # we expect ./gitlab-artifacts/*/artifacts/pypi-id-token to exist | |
| pypi_id_token_file=$(find ./gitlab-artifacts -type f -name pypi-id-token | head -n 1) | |
| if [ -z "${pypi_id_token_file}" ]; then | |
| echo "No pypi-id-token file found in GitLab artifacts" | |
| exit 1 | |
| fi | |
| GITLAB_PYPI_OIDC_TOKEN=$(cat "${pypi_id_token_file}") | |
| # we expect ./gitlab-artifacts/*/artifacts/pyx-id-token to exist | |
| pyx_id_token_file=$(find ./gitlab-artifacts -type f -name pyx-id-token | head -n 1) | |
| if [ -z "${pyx_id_token_file}" ]; then | |
| echo "No pyx-id-token file found in GitLab artifacts" | |
| exit 1 | |
| fi | |
| GITLAB_PYX_OIDC_TOKEN=$(cat "${pyx_id_token_file}") | |
| # Add secret masks for the tokens. | |
| echo "::add-mask::$GITLAB_PYPI_OIDC_TOKEN" | |
| echo "::add-mask::$GITLAB_PYX_OIDC_TOKEN" | |
| echo "GITLAB_PYPI_OIDC_TOKEN=${GITLAB_PYPI_OIDC_TOKEN}" >> "${GITHUB_OUTPUT}" | |
| echo "GITLAB_PYX_OIDC_TOKEN=${GITLAB_PYX_OIDC_TOKEN}" >> "${GITHUB_OUTPUT}" | |
| - name: "Add password to keyring" | |
| run: | | |
| # `keyrings.alt` contains the plaintext keyring | |
| ./uv tool install --with keyrings.alt keyring | |
| echo $UV_TEST_PUBLISH_KEYRING | keyring set https://test.pypi.org/legacy/?astral-test-keyring __token__ | |
| env: | |
| UV_TEST_PUBLISH_KEYRING: ${{ secrets.UV_TEST_PUBLISH_KEYRING }} | |
| - name: "Add password to uv text store" | |
| run: | | |
| ./uv auth login https://test.pypi.org/legacy/?astral-test-text-store --token ${UV_TEST_PUBLISH_TEXT_STORE} | |
| env: | |
| UV_TEST_PUBLISH_TEXT_STORE: ${{ secrets.UV_TEST_PUBLISH_TEXT_STORE }} | |
| - name: "Publish test packages" | |
| # `-p 3.12` prefers the python we just installed over the one locked in `.python_version`. | |
| run: ./uv run -p "${PYTHON_VERSION}" scripts/publish/test_publish.py --uv ./uv all | |
| env: | |
| RUST_LOG: uv=debug,uv_publish=trace | |
| 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 }} | |
| UV_TEST_PUBLISH_PYX_TOKEN: ${{ secrets.UV_TEST_PUBLISH_PYX_TOKEN }} | |
| UV_TEST_PUBLISH_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} | |
| UV_TEST_PUBLISH_GITLAB_PYPI_OIDC_TOKEN: ${{ steps.load-gitlab-oidc-token.outputs.GITLAB_PYPI_OIDC_TOKEN }} | |
| UV_TEST_PUBLISH_GITLAB_PYX_OIDC_TOKEN: ${{ steps.load-gitlab-oidc-token.outputs.GITLAB_PYX_OIDC_TOKEN }} | |
| required-checks-passed: | |
| name: "all required jobs passed" | |
| if: always() | |
| needs: | |
| - check-fmt | |
| - check-lint | |
| - check-docs | |
| - check-generated-files | |
| - test | |
| - build-dev-binaries | |
| runs-on: ubuntu-slim | |
| steps: | |
| - name: "Check required jobs passed" | |
| run: | | |
| failing=$(echo "$NEEDS_JSON" | jq -r 'to_entries[] | select(.value.result != "success" and .value.result != "skipped") | "\(.key): \(.value.result)"') | |
| if [ -n "$failing" ]; then | |
| echo "$failing" | |
| exit 1 | |
| fi | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} |