astral-sh/uv

25 workflows · maturity 67% · 14 patterns · GitHub ↗

Security 42/100

Security dimensions

permissions
22
security scan
0
supply chain
20
secret handling
0
harden runner
0

Workflows (25)

bench perms .github/workflows/bench.yml
Triggers
workflow_call
Runs on
depot-ubuntu-22.04-arm-4, codspeed-macro, depot-ubuntu-24.04-4
Jobs
benchmarks-walltime-build, benchmarks-walltime-run, benchmarks-simulated
Actions
Swatinem/rust-cache, taiki-e/install-action, taiki-e/install-action, CodSpeedHQ/action, Swatinem/rust-cache, taiki-e/install-action, CodSpeedHQ/action
Commands
  • rustup show
  • sudo apt-get update sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev cargo run --bin uv -- venv --cache-dir .cache cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
  • cargo codspeed build -m walltime --profile profiling -p uv-bench
  • tar -cvf benchmarks-walltime.tar target/codspeed target/debug/uv .cache
  • tar -xvf benchmarks-walltime.tar
  • ./target/debug/uv venv --cache-dir .cache
  • rustup show
  • sudo apt-get update sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev cargo run --bin uv -- venv --cache-dir .cache cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
View raw YAML
on:
  workflow_call:
    inputs:
      save-rust-cache:
        required: false
        type: string
        default: "true"

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  benchmarks-walltime-build:
    name: "walltime build"
    # codspeed-macro doesn't support Ubuntu 24.04 yet
    runs-on: depot-ubuntu-22.04-arm-4
    if: ${{ github.repository == 'astral-sh/uv' }}
    timeout-minutes: 20
    steps:
      - name: "Checkout Branch"
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Install Rust toolchain"
        run: rustup show

      - name: "Install codspeed"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-codspeed

      - name: "Install requirements and prime cache"
        run: |
          sudo apt-get update
          sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev
          cargo run --bin uv -- venv --cache-dir .cache
          cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
          cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache

      - name: "Build benchmarks"
        run: cargo codspeed build -m walltime --profile profiling -p uv-bench

      - name: "Create artifact archive"
        run: tar -cvf benchmarks-walltime.tar target/codspeed target/debug/uv .cache

      - name: "Upload benchmark artifacts"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: benchmarks-walltime
          path: benchmarks-walltime.tar
          retention-days: 1

  benchmarks-walltime-run:
    name: "walltime on aarch64 linux"
    runs-on: codspeed-macro
    needs: benchmarks-walltime-build
    timeout-minutes: 20
    steps:
      - name: "Checkout Branch"
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install codspeed"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-codspeed

      - name: "Download benchmark artifacts"
        uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
        with:
          name: benchmarks-walltime

      - name: "Extract artifact archive"
        run: tar -xvf benchmarks-walltime.tar

      - name: "Create venv"
        run: ./target/debug/uv venv --cache-dir .cache

      - name: "Run benchmarks"
        uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4.11.1
        with:
          run: cargo codspeed run
          mode: walltime
          token: ${{ secrets.CODSPEED_TOKEN }}

  benchmarks-simulated:
    name: "simulated"
    runs-on: depot-ubuntu-24.04-4
    if: ${{ github.repository == 'astral-sh/uv' }}
    timeout-minutes: 20
    steps:
      - name: "Checkout Branch"
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Install Rust toolchain"
        run: rustup show

      - name: "Install codspeed"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-codspeed

      - name: "Install requirements and prime cache"
        run: |
          sudo apt-get update
          sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev
          cargo run --bin uv -- venv --cache-dir .cache
          cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
          cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache

      - name: "Build benchmarks"
        run: cargo codspeed build --profile profiling -p uv-bench

      - name: "Run benchmarks"
        uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4.11.1
        with:
          run: cargo codspeed run
          mode: simulation
          token: ${{ secrets.CODSPEED_TOKEN }}
build-dev-binaries perms .github/workflows/build-dev-binaries.yml
Triggers
workflow_call
Runs on
github-ubuntu-24.04-x86_64-8, github-ubuntu-24.04-aarch64-4, github-ubuntu-24.04-x86_64-8, github-ubuntu-24.04-x86_64-8, macos-14, macos-latest-large, windows-latest, windows-11-arm, github-ubuntu-24.04-x86_64-8, github-ubuntu-24.04-x86_64-8, ubuntu-latest
Jobs
build-binary-linux-libc, build-binary-linux-aarch64, build-binary-linux-armv7-gnueabihf, build-binary-linux-musl, build-binary-macos-aarch64, build-binary-macos-x86_64, build-binary-windows-x86_64, build-binary-windows-aarch64, build-binary-msrv, build-binary-android-aarch64, build-binary-freebsd
Actions
Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, Swatinem/rust-cache, acj/freebsd-firecracker-action
Commands
  • ./scripts/install-mold.sh
  • cargo build --profile no-debug
  • ./scripts/install-mold.sh
  • cargo build --profile no-debug
  • ./scripts/install-mold.sh
  • sudo apt-get update sudo apt-get install gcc-arm-linux-gnueabihf rustup target add armv7-unknown-linux-gnueabihf
  • cargo build --profile no-debug --target armv7-unknown-linux-gnueabihf --bin uv --bin uvx
  • ./scripts/install-mold.sh
View raw YAML
on:
  workflow_call:
    inputs:
      save-rust-cache:
        required: false
        type: string
        default: "true"

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  build-binary-linux-libc:
    name: "linux libc"
    timeout-minutes: 10
    runs-on: github-ubuntu-24.04-x86_64-8
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install mold"
        run: ./scripts/install-mold.sh

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Build"
        run: cargo build --profile no-debug

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-linux-libc-${{ github.sha }}
          path: |
            ./target/no-debug/uv
            ./target/no-debug/uvx
          retention-days: 1

  build-binary-linux-aarch64:
    name: "linux aarch64"
    timeout-minutes: 10
    runs-on: github-ubuntu-24.04-aarch64-4
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install mold"
        run: ./scripts/install-mold.sh

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Build"
        run: cargo build --profile no-debug

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-linux-aarch64-${{ github.sha }}
          path: |
            ./target/no-debug/uv
            ./target/no-debug/uvx
          retention-days: 1

  build-binary-linux-armv7-gnueabihf:
    name: "linux armv7 gnueabihf"
    timeout-minutes: 15
    runs-on: github-ubuntu-24.04-x86_64-8
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install mold"
        run: ./scripts/install-mold.sh

      - name: "Setup armv7"
        run: |
          sudo apt-get update
          sudo apt-get install gcc-arm-linux-gnueabihf
          rustup target add armv7-unknown-linux-gnueabihf

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Build"
        env:
          CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
        run: cargo build --profile no-debug --target armv7-unknown-linux-gnueabihf --bin uv --bin uvx

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-linux-armv7-gnueabihf-${{ github.sha }}
          path: |
            ./target/armv7-unknown-linux-gnueabihf/no-debug/uv
            ./target/armv7-unknown-linux-gnueabihf/no-debug/uvx
          retention-days: 1

  build-binary-linux-musl:
    name: "linux musl"
    timeout-minutes: 10
    runs-on: github-ubuntu-24.04-x86_64-8
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install mold"
        run: ./scripts/install-mold.sh

      - name: "Setup musl"
        run: |
          sudo apt-get install musl-tools
          rustup target add x86_64-unknown-linux-musl

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Build"
        run: cargo build --profile no-debug --target x86_64-unknown-linux-musl --bin uv --bin uvx

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-linux-musl-${{ github.sha }}
          path: |
            ./target/x86_64-unknown-linux-musl/no-debug/uv
            ./target/x86_64-unknown-linux-musl/no-debug/uvx
          retention-days: 1

  build-binary-macos-aarch64:
    name: "macos aarch64"
    timeout-minutes: 10
    runs-on: macos-14 # github-macos-14-aarch64-3
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}
      - name: "Build"
        run: cargo build --profile no-debug --bin uv --bin uvx

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-macos-aarch64-${{ github.sha }}
          path: |
            ./target/no-debug/uv
            ./target/no-debug/uvx
          retention-days: 1

  build-binary-macos-x86_64:
    name: "macos x86_64"
    timeout-minutes: 10
    runs-on: macos-latest-large # github-macos-14-x86_64-12
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}
      - name: "Build"
        run: cargo build --profile no-debug --bin uv --bin uvx

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-macos-x86_64-${{ github.sha }}
          path: |
            ./target/no-debug/uv
            ./target/no-debug/uvx
          retention-days: 1

  build-binary-windows-x86_64:
    name: "windows x86_64"
    timeout-minutes: 10
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup Dev Drive
        run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1

      # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
      - name: Copy Git Repo to Dev Drive
        run: |
          Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: ${{ env.UV_WORKSPACE }}
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Build"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: cargo build --profile no-debug --bin uv --bin uvx

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-windows-x86_64-${{ github.sha }}
          path: |
            ${{ env.UV_WORKSPACE }}/target/no-debug/uv.exe
            ${{ env.UV_WORKSPACE }}/target/no-debug/uvx.exe
          retention-days: 1

  build-binary-windows-aarch64:
    name: "windows aarch64"
    timeout-minutes: 10
    runs-on: windows-11-arm
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup Dev Drive
        run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1

      # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
      - name: Copy Git Repo to Dev Drive
        run: |
          Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: ${{ env.UV_WORKSPACE }}
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Build"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: cargo build --profile no-debug --bin uv --bin uvx

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-windows-aarch64-${{ github.sha }}
          path: |
            ${{ env.UV_WORKSPACE }}/target/no-debug/uv.exe
            ${{ env.UV_WORKSPACE }}/target/no-debug/uvx.exe
          retention-days: 1

  build-binary-msrv:
    name: "msrv"
    runs-on: github-ubuntu-24.04-x86_64-8
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Read MSRV from Cargo.toml"
        id: msrv
        run: |
          MSRV=$(grep -m1 'rust-version' Cargo.toml | sed 's/.*"\([^"]*\)".*/\1/')
          echo "value=$MSRV" >> "$GITHUB_OUTPUT"
      - name: "Install Rust toolchain"
        run: rustup default ${MSRV}
        env:
          MSRV: ${{ steps.msrv.outputs.value }}
      - name: "Install mold"
        run: ./scripts/install-mold.sh
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}
      - run: cargo +${MSRV} build --profile no-debug
        env:
          MSRV: ${{ steps.msrv.outputs.value }}
      - run: ./target/no-debug/uv --version

  build-binary-android-aarch64:
    name: "android aarch64"
    timeout-minutes: 10
    runs-on: github-ubuntu-24.04-x86_64-8
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Setup Android NDK"
        run: |
          # Use ANDROID_NDK_ROOT if set, otherwise find the latest installed NDK
          if [ -z "${ANDROID_NDK_ROOT}" ]; then
            NDK_ROOT=$(ls -d "${ANDROID_HOME}/ndk/"* 2>/dev/null | sort -V | tail -n1)
          else
            NDK_ROOT="${ANDROID_NDK_ROOT}"
          fi
          TOOLCHAIN="${NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64"

          echo "CC_aarch64_linux_android=${TOOLCHAIN}/bin/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
          echo "CXX_aarch64_linux_android=${TOOLCHAIN}/bin/aarch64-linux-android24-clang++" >> "$GITHUB_ENV"
          echo "AR_aarch64_linux_android=${TOOLCHAIN}/bin/llvm-ar" >> "$GITHUB_ENV"
          echo "RANLIB_aarch64_linux_android=${TOOLCHAIN}/bin/llvm-ranlib" >> "$GITHUB_ENV"
          echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${TOOLCHAIN}/bin/aarch64-linux-android24-clang" >> "$GITHUB_ENV"
          echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_RANLIB=${TOOLCHAIN}/bin/llvm-ranlib" >> "$GITHUB_ENV"

          # NDK 23+ removed libgcc, provide a stub that redirects to libunwind
          LIBDIR=$(echo "${TOOLCHAIN}"/lib/clang/*/lib/linux/aarch64)
          if [ ! -f "${LIBDIR}/libgcc.a" ]; then
            echo 'INPUT(-lunwind)' | sudo tee "${LIBDIR}/libgcc.a" > /dev/null
          fi

          rustup target add aarch64-linux-android

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Build"
        run: cargo build --profile no-debug --target aarch64-linux-android --bin uv --bin uvx

      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: uv-android-aarch64-${{ github.sha }}
          path: |
            ./target/aarch64-linux-android/no-debug/uv
            ./target/aarch64-linux-android/no-debug/uvx
          retention-days: 1

  build-binary-freebsd:
    name: "freebsd"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}
      - name: "Cross build"
        run: |
          # Install cross from `freebsd-firecracker`
          wget -q -O cross https://github.com/acj/freebsd-firecracker/releases/download/v0.0.10/cross
          chmod +x cross
          mv cross /usr/local/bin/cross

          cross build --target x86_64-unknown-freebsd --profile no-debug

      - name: Test in Firecracker VM
        uses: acj/freebsd-firecracker-action@1cc93bd507a8376bddcd15a2b9c660394dc60456 # v0.9.0
        with:
          verbose: false
          checkout: false
          pre-run: |
            # The exclude `*` prevents examination of directories so we need to
            # include each parent directory of the binary
            include_path="$(mktemp)"
            cat <<EOF > $include_path
            target
            target/x86_64-unknown-freebsd
            target/x86_64-unknown-freebsd/no-debug
            target/x86_64-unknown-freebsd/no-debug/uv
            EOF

            rsync -r -e "ssh" \
              --relative \
              --copy-links \
              --include-from "$include_path" \
              --exclude "*" \
              . firecracker:
          run-in-vm: |
            mv target/x86_64-unknown-freebsd/no-debug/uv uv
            chmod +x uv
            ./uv --version
build-docker matrix perms .github/workflows/build-docker.yml
Triggers
workflow_call
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
docker-plan, docker-publish-base, docker-publish-extra, docker-annotate-base
Matrix
Actions
docker/login-action, docker/login-action, depot/setup-action, docker/metadata-action, depot/build-push-action, actions/attest-build-provenance, docker/login-action, docker/login-action, docker/login-action, depot/setup-action, docker/metadata-action, depot/build-push-action, actions/attest-build-provenance, actions/attest-build-provenance, docker/login-action, docker/login-action, actions/attest-build-provenance
Commands
  • if [ "${PUSH_DEV}" == "true" ] && [ "${IS_LOCAL_PR}" == "true" ]; then echo "login=true" >> "$GITHUB_OUTPUT" echo "push=true" >> "$GITHUB_OUTPUT" echo "push-version=false" >> "$GITHUB_OUTPUT" echo "tag=sha" >> "$GITHUB_OUTPUT" echo "action=build and publish to uv-dev" >> "$GITHUB_OUTPUT" elif [ "${DRY_RUN}" == "false" ]; then echo "login=true" >> "$GITHUB_OUTPUT" echo "push=true" >> "$GITHUB_OUTPUT" echo "push-version=true" >> "$GITHUB_OUTPUT" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" echo "action=build and publish" >> "$GITHUB_OUTPUT" else echo "login=${IS_LOCAL_PR}" >> "$GITHUB_OUTPUT" echo "push=false" >> "$GITHUB_OUTPUT" echo "push-version=false" >> "$GITHUB_OUTPUT" echo "tag=dry-run" >> "$GITHUB_OUTPUT" echo "action=build" >> "$GITHUB_OUTPUT" fi
  • # Each entry is: base-image,tag1,tag2,... # DHI (Docker Hardened Images) require authentication to pull, so they # are excluded when login is unavailable (e.g., PRs from forks). images=( "alpine:3.23,alpine3.23,alpine" "alpine:3.22,alpine3.22" "debian:trixie-slim,trixie-slim,debian-slim" "buildpack-deps:trixie,trixie,debian" "python:3.14-alpine3.23,python3.14-alpine3.23,python3.14-alpine" "python:3.13-alpine3.23,python3.13-alpine3.23,python3.13-alpine" "python:3.12-alpine3.23,python3.12-alpine3.23,python3.12-alpine" "python:3.11-alpine3.23,python3.11-alpine3.23,python3.11-alpine" "python:3.10-alpine3.23,python3.10-alpine3.23,python3.10-alpine" "python:3.9-alpine3.22,python3.9-alpine3.22,python3.9-alpine" "python:3.14-trixie,python3.14-trixie" "python:3.13-trixie,python3.13-trixie" "python:3.12-trixie,python3.12-trixie" "python:3.11-trixie,python3.11-trixie" "python:3.10-trixie,python3.10-trixie" "python:3.9-trixie,python3.9-trixie" "python:3.14-slim-trixie,python3.14-trixie-slim" "python:3.13-slim-trixie,python3.13-trixie-slim" "python:3.12-slim-trixie,python3.12-trixie-slim" "python:3.11-slim-trixie,python3.11-trixie-slim" "python:3.10-slim-trixie,python3.10-trixie-slim" "python:3.9-slim-trixie,python3.9-trixie-slim" ) if [ "${LOGIN}" == "true" ]; then images+=( "dhi.io/alpine-base:3.23,alpine3.23-dhi,alpine-dhi" "dhi.io/debian-base:trixie-debian13,trixie-dhi,debian-dhi" "dhi.io/python:3.14,python3.14-dhi" "dhi.io/python:3.13,python3.13-dhi" "dhi.io/python:3.12,python3.12-dhi" "dhi.io/python:3.11,python3.11-dhi" "dhi.io/python:3.10,python3.10-dhi" ) fi json=$(printf '%s\n' "${images[@]}" | jq -R . | jq -sc '{"image-mapping": .}') echo "matrix=${json}" >> "$GITHUB_OUTPUT"
  • if [ "${PUSH_DEV}" == "true" ]; then echo "Building at $(git rev-parse HEAD)" exit 0 fi version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g') if [ "${TAG}" != "${version}" ]; then echo "The input tag does not match the version from pyproject.toml:" >&2 echo "${TAG}" >&2 echo "${version}" >&2 exit 1 fi echo "Releasing ${version}"
  • set -euo pipefail # Extract the image and tags from the matrix variable IFS=',' read -r BASE_IMAGE BASE_TAGS <<< "${IMAGE_MAPPING}" # Generate Dockerfile content cat <<EOF > Dockerfile FROM ${BASE_IMAGE} COPY --from=${UV_GHCR_IMAGE}:${UV_BASE_TAG} /uv /uvx /usr/local/bin/ ENV UV_TOOL_BIN_DIR="/usr/local/bin" ENTRYPOINT [] CMD ["/usr/local/bin/uv"] EOF # Initialize a variable to store all tag docker metadata patterns TAG_PATTERNS="" # Loop through all base tags and append its docker metadata pattern to the list # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version IFS=','; for TAG in ${BASE_TAGS}; do if [ "${PUSH_DEV}" == "true" ]; then TAG_PATTERNS="${TAG_PATTERNS}type=sha,suffix=-${TAG}\n" else TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${VERSION}\n" TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${VERSION}\n" TAG_PATTERNS="${TAG_PATTERNS}type=raw,value=${TAG}\n" fi done # Remove the trailing newline from the pattern list TAG_PATTERNS="${TAG_PATTERNS%\\n}" # Export tag patterns using the multiline env var syntax { echo "TAG_PATTERNS<<EOF" echo -e "${TAG_PATTERNS}" echo EOF } >> $GITHUB_ENV
  • set -x readarray -t lines <<< "$ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done for image in $IMAGES; do readarray -t lines < <(grep "^${image}:" <<< "$TAGS"); tags=(); for line in "${lines[@]}"; do tags+=(-t "$line"); done docker buildx imagetools create \ "${annotations[@]}" \ "${tags[@]}" \ "${image}@${DIGEST}" done
  • digest="$( docker buildx imagetools inspect \ "${IMAGE}:${VERSION}" \ --format '{{json .Manifest}}' \ | jq -r '.digest' )" echo "digest=${digest}" >> "$GITHUB_OUTPUT"
  • set -x readarray -t lines <<< "$ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done for image in $IMAGES; do readarray -t lines < <(grep "^${image}:" <<< "$TAGS"); tags=(); for line in "${lines[@]}"; do tags+=(-t "$line"); done docker buildx imagetools create \ "${annotations[@]}" \ "${tags[@]}" \ "${image}@${DIGEST}" done
  • digest="$( docker buildx imagetools inspect \ "${IMAGE}:${VERSION}" \ --format '{{json .Manifest}}' \ | jq -r '.digest' )" echo "digest=${digest}" >> "$GITHUB_OUTPUT"
View raw YAML
# Build and publish Docker images.
#
# Uses Depot for multi-platform builds. Includes both a `uv` base image, which
# is just the binary in a scratch image, and a set of extra, common images with
# the uv binary installed.
#
# On pull requests, triggered via CI workflow when Docker-related files change
# (e.g., Dockerfile, Cargo.toml, rust-toolchain.toml). Images are built but not
# pushed, to verify the build still works.
#
# On release, assumed to run as a subworkflow of .github/workflows/release.yml;
# specifically, as a local artifacts job within `cargo-dist`. In this case,
# images are published based on the `plan`.
#
# TODO(charlie): Ideally, the publish step would happen as a publish job within
# `cargo-dist`, but sharing the built image as an artifact between jobs is
# challenging.
name: "Docker images"

on:
  workflow_call:
    inputs:
      plan:
        required: false
        type: string
        default: ""
      push-dev:
        required: false
        type: boolean
        default: false

env:
  UV_GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ inputs.push-dev && 'uv-dev' || 'uv' }}
  UV_DOCKERHUB_IMAGE: ${{ !inputs.push-dev && 'docker.io/astral/uv' || '' }}

permissions: {}

jobs:
  docker-plan:
    name: plan
    runs-on: ubuntu-latest
    timeout-minutes: 2
    outputs:
      login: ${{ steps.plan.outputs.login }}
      push: ${{ steps.plan.outputs.push }}
      push-version: ${{ steps.plan.outputs.push-version }}
      tag: ${{ steps.plan.outputs.tag }}
      action: ${{ steps.plan.outputs.action }}
      extra-images: ${{ steps.extra-images.outputs.matrix }}
    steps:
      - name: Set push variable
        env:
          DRY_RUN: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}
          TAG: ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag }}
          PUSH_DEV: ${{ inputs.push-dev }}
          IS_LOCAL_PR: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }}
        id: plan
        run: |
          if [ "${PUSH_DEV}" == "true" ] && [ "${IS_LOCAL_PR}" == "true" ]; then
            echo "login=true" >> "$GITHUB_OUTPUT"
            echo "push=true" >> "$GITHUB_OUTPUT"
            echo "push-version=false" >> "$GITHUB_OUTPUT"
            echo "tag=sha" >> "$GITHUB_OUTPUT"
            echo "action=build and publish to uv-dev" >> "$GITHUB_OUTPUT"
          elif [ "${DRY_RUN}" == "false" ]; then
            echo "login=true" >> "$GITHUB_OUTPUT"
            echo "push=true" >> "$GITHUB_OUTPUT"
            echo "push-version=true" >> "$GITHUB_OUTPUT"
            echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
            echo "action=build and publish" >> "$GITHUB_OUTPUT"
          else
            echo "login=${IS_LOCAL_PR}" >> "$GITHUB_OUTPUT"
            echo "push=false" >> "$GITHUB_OUTPUT"
            echo "push-version=false" >> "$GITHUB_OUTPUT"
            echo "tag=dry-run" >> "$GITHUB_OUTPUT"
            echo "action=build" >> "$GITHUB_OUTPUT"
          fi

      - name: Generate extra image matrix
        id: extra-images
        env:
          LOGIN: ${{ steps.plan.outputs.login }}
        run: |
          # Each entry is: base-image,tag1,tag2,...
          # DHI (Docker Hardened Images) require authentication to pull, so they
          # are excluded when login is unavailable (e.g., PRs from forks).
          images=(
            "alpine:3.23,alpine3.23,alpine"
            "alpine:3.22,alpine3.22"
            "debian:trixie-slim,trixie-slim,debian-slim"
            "buildpack-deps:trixie,trixie,debian"
            "python:3.14-alpine3.23,python3.14-alpine3.23,python3.14-alpine"
            "python:3.13-alpine3.23,python3.13-alpine3.23,python3.13-alpine"
            "python:3.12-alpine3.23,python3.12-alpine3.23,python3.12-alpine"
            "python:3.11-alpine3.23,python3.11-alpine3.23,python3.11-alpine"
            "python:3.10-alpine3.23,python3.10-alpine3.23,python3.10-alpine"
            "python:3.9-alpine3.22,python3.9-alpine3.22,python3.9-alpine"
            "python:3.14-trixie,python3.14-trixie"
            "python:3.13-trixie,python3.13-trixie"
            "python:3.12-trixie,python3.12-trixie"
            "python:3.11-trixie,python3.11-trixie"
            "python:3.10-trixie,python3.10-trixie"
            "python:3.9-trixie,python3.9-trixie"
            "python:3.14-slim-trixie,python3.14-trixie-slim"
            "python:3.13-slim-trixie,python3.13-trixie-slim"
            "python:3.12-slim-trixie,python3.12-trixie-slim"
            "python:3.11-slim-trixie,python3.11-trixie-slim"
            "python:3.10-slim-trixie,python3.10-trixie-slim"
            "python:3.9-slim-trixie,python3.9-trixie-slim"
          )

          if [ "${LOGIN}" == "true" ]; then
            images+=(
              "dhi.io/alpine-base:3.23,alpine3.23-dhi,alpine-dhi"
              "dhi.io/debian-base:trixie-debian13,trixie-dhi,debian-dhi"
              "dhi.io/python:3.14,python3.14-dhi"
              "dhi.io/python:3.13,python3.13-dhi"
              "dhi.io/python:3.12,python3.12-dhi"
              "dhi.io/python:3.11,python3.11-dhi"
              "dhi.io/python:3.10,python3.10-dhi"
            )
          fi

          json=$(printf '%s\n' "${images[@]}" | jq -R . | jq -sc '{"image-mapping": .}')
          echo "matrix=${json}" >> "$GITHUB_OUTPUT"

  docker-publish-base:
    name: ${{ needs.docker-plan.outputs.action }} uv
    needs:
      - docker-plan
    runs-on: ubuntu-latest
    timeout-minutes: 20
    permissions:
      contents: read
      id-token: write # for Depot OIDC and GHCR signing
      packages: write # for GHCR image pushes
      attestations: write # for GHCR attestations
    environment:
      name: ${{ needs.docker-plan.outputs.push-version == 'true' && 'release' || (needs.docker-plan.outputs.push == 'true' && 'release-test' || '') }}
      deployment: false
    outputs:
      image-tags: ${{ steps.meta.outputs.tags }}
      image-annotations: ${{ steps.meta.outputs.annotations }}
      image-digest: ${{ steps.build.outputs.digest }}
      image-version: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false

      # Login to DockerHub (when not pushing, it's to avoid rate-limiting)
      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        if: ${{ needs.docker-plan.outputs.login == 'true' }}
        with:
          username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }}
          password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }}

      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        if: ${{ needs.docker-plan.outputs.push == 'true' }}
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5

      - name: Check tag consistency
        if: ${{ needs.docker-plan.outputs.push == 'true' }}
        run: |
          if [ "${PUSH_DEV}" == "true" ]; then
            echo "Building at $(git rev-parse HEAD)"
            exit 0
          fi

          version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
          if [ "${TAG}" != "${version}" ]; then
            echo "The input tag does not match the version from pyproject.toml:" >&2
            echo "${TAG}" >&2
            echo "${version}" >&2
            exit 1
          fi
          echo "Releasing ${version}"
        env:
          TAG: ${{ needs.docker-plan.outputs.tag }}
          PUSH_DEV: ${{ inputs.push-dev }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: index
        with:
          images: |
            ${{ env.UV_GHCR_IMAGE }}
            ${{ env.UV_DOCKERHUB_IMAGE }}
          # Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
          tags: |
            type=raw,value=dry-run,enable=${{ needs.docker-plan.outputs.push == 'false' }}
            type=pep440,pattern={{ version }},value=${{ needs.docker-plan.outputs.tag }},enable=${{ needs.docker-plan.outputs.push-version == 'true' }}
            type=pep440,pattern={{ major }}.{{ minor }},value=${{ needs.docker-plan.outputs.tag }},enable=${{ needs.docker-plan.outputs.push-version == 'true' }}
            type=sha,enable=${{ inputs.push-dev }}

      - name: Build and push by digest
        id: build
        uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
        with:
          project: 7hd4vdzmw5 # astral-sh/uv
          context: .
          platforms: linux/amd64,linux/arm64
          push: ${{ needs.docker-plan.outputs.push }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          provenance: mode=max
          sbom: true
          # TODO(zanieb): Annotations are not supported by Depot yet and are ignored
          annotations: ${{ steps.meta.outputs.annotations }}

      - name: Generate artifact attestation for base image
        if: ${{ needs.docker-plan.outputs.push == 'true' }}
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.UV_GHCR_IMAGE }}
          subject-digest: ${{ steps.build.outputs.digest }}

  docker-publish-extra:
    name: ${{ needs.docker-plan.outputs.action }} ${{ matrix.image-mapping }}
    runs-on: ubuntu-latest
    timeout-minutes: 5
    environment:
      name: ${{ needs.docker-plan.outputs.push-version == 'true' && 'release' || (needs.docker-plan.outputs.push == 'true' && 'release-test' || '') }}
      deployment: false
    needs:
      - docker-plan
      - docker-publish-base
    permissions:
      id-token: write # for Depot OIDC and GHCR signing
      packages: write # for GHCR image pushes
      attestations: write # for GHCR attestations
    strategy:
      fail-fast: false
      matrix: ${{ fromJson(needs.docker-plan.outputs.extra-images) }}
    steps:
      # Login to DockerHub (when not pushing, it's to avoid rate-limiting)
      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        if: ${{ needs.docker-plan.outputs.login == 'true' }}
        with:
          username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }}
          password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }}

      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        if: ${{ needs.docker-plan.outputs.login == 'true' }}
        with:
          registry: dhi.io
          username: astralshbot
          password: ${{ secrets.DOCKERHUB_TOKEN_RO }}

      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - uses: depot/setup-action@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5

      - name: Generate Dynamic Dockerfile Tags
        shell: bash
        run: |
          set -euo pipefail

          # Extract the image and tags from the matrix variable
          IFS=',' read -r BASE_IMAGE BASE_TAGS <<< "${IMAGE_MAPPING}"

          # Generate Dockerfile content
          cat <<EOF > Dockerfile
          FROM ${BASE_IMAGE}
          COPY --from=${UV_GHCR_IMAGE}:${UV_BASE_TAG} /uv /uvx /usr/local/bin/
          ENV UV_TOOL_BIN_DIR="/usr/local/bin"
          ENTRYPOINT []
          CMD ["/usr/local/bin/uv"]
          EOF

          # Initialize a variable to store all tag docker metadata patterns
          TAG_PATTERNS=""

          # Loop through all base tags and append its docker metadata pattern to the list
          # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
          IFS=','; for TAG in ${BASE_TAGS}; do
            if [ "${PUSH_DEV}" == "true" ]; then
              TAG_PATTERNS="${TAG_PATTERNS}type=sha,suffix=-${TAG}\n"
            else
              TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${VERSION}\n"
              TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${VERSION}\n"
              TAG_PATTERNS="${TAG_PATTERNS}type=raw,value=${TAG}\n"
            fi
          done

          # Remove the trailing newline from the pattern list
          TAG_PATTERNS="${TAG_PATTERNS%\\n}"

          # Export tag patterns using the multiline env var syntax
          {
            echo "TAG_PATTERNS<<EOF"
            echo -e "${TAG_PATTERNS}"
            echo EOF
          } >> $GITHUB_ENV
        env:
          VERSION: ${{ needs.docker-plan.outputs.tag }}
          PUSH_DEV: ${{ inputs.push-dev }}
          # Use the tag from the base image we just pushed; fall back to `latest` on dry-runs
          # since the base image isn't pushed to the registry.
          UV_BASE_TAG: ${{ needs.docker-plan.outputs.push == 'true' && needs.docker-publish-base.outputs.image-version || 'latest' }}
          IMAGE_MAPPING: ${{ matrix.image-mapping }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        # ghcr.io prefers index level annotations
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: index
        with:
          images: |
            ${{ env.UV_GHCR_IMAGE }}
            ${{ env.UV_DOCKERHUB_IMAGE }}
          flavor: |
            latest=false
          tags: |
            ${{ env.TAG_PATTERNS }}

      - name: Build and push
        id: build-and-push
        uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
        with:
          context: .
          project: 7hd4vdzmw5 # astral-sh/uv
          platforms: linux/amd64,linux/arm64
          push: ${{ needs.docker-plan.outputs.push }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          provenance: mode=max
          sbom: true
          # TODO(zanieb): Annotations are not supported by Depot yet and are ignored
          annotations: ${{ steps.meta.outputs.annotations }}

      - name: Generate artifact attestation
        if: ${{ needs.docker-plan.outputs.push == 'true' }}
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.UV_GHCR_IMAGE }}
          subject-digest: ${{ steps.build-and-push.outputs.digest }}

      # Push annotations manually.
      # See `docker-annotate-base` for details.
      - name: Add annotations to images
        if: ${{ needs.docker-plan.outputs.push == 'true' }}
        env:
          IMAGES: "${{ env.UV_GHCR_IMAGE }} ${{ env.UV_DOCKERHUB_IMAGE }}"
          DIGEST: ${{ steps.build-and-push.outputs.digest }}
          TAGS: ${{ steps.meta.outputs.tags }}
          ANNOTATIONS: ${{ steps.meta.outputs.annotations }}
        run: |
          set -x
          readarray -t lines <<< "$ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done
          for image in $IMAGES; do
            readarray -t lines < <(grep "^${image}:" <<< "$TAGS"); tags=(); for line in "${lines[@]}"; do tags+=(-t "$line"); done
            docker buildx imagetools create \
              "${annotations[@]}" \
              "${tags[@]}" \
              "${image}@${DIGEST}"
          done

      # See `docker-annotate-base` for details.
      - name: Export manifest digest
        id: manifest-digest
        if: ${{ needs.docker-plan.outputs.push == 'true' }}
        env:
          IMAGE: ${{ env.UV_GHCR_IMAGE }}
          VERSION: ${{ steps.meta.outputs.version }}
        run: |
          digest="$(
            docker buildx imagetools inspect \
              "${IMAGE}:${VERSION}" \
              --format '{{json .Manifest}}' \
            | jq -r '.digest'
          )"
          echo "digest=${digest}" >> "$GITHUB_OUTPUT"

      # See `docker-annotate-base` for details.
      - name: Generate artifact attestation
        if: ${{ needs.docker-plan.outputs.push == 'true' }}
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.UV_GHCR_IMAGE }}
          subject-digest: ${{ steps.manifest-digest.outputs.digest }}

  # Annotate the base image
  docker-annotate-base:
    name: annotate uv
    runs-on: ubuntu-latest
    timeout-minutes: 2
    permissions:
      contents: read
      id-token: write # for GHCR signing
      packages: write # for GHCR image pushes
      attestations: write # for GHCR attestations
    environment:
      name: ${{ needs.docker-plan.outputs.push-version == 'true' && 'release' || (needs.docker-plan.outputs.push == 'true' && 'release-test' || '') }}
      deployment: false
    needs:
      - docker-plan
      - docker-publish-base
      - docker-publish-extra
    if: ${{ needs.docker-plan.outputs.push == 'true' }}
    steps:
      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          username: astral
          password: ${{ secrets.DOCKERHUB_TOKEN_RW }}

      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Depot doesn't support annotating images, so we need to do so manually
      # afterwards. Mutating the manifest is desirable regardless, because we
      # want to bump the base image to appear at the top of the list on GHCR.
      # However, once annotation support is added to Depot, this step can be
      # minimized to just touch the GHCR manifest.
      - name: Add annotations to images
        env:
          IMAGES: "${{ env.UV_GHCR_IMAGE }} ${{ env.UV_DOCKERHUB_IMAGE }}"
          DIGEST: ${{ needs.docker-publish-base.outputs.image-digest }}
          TAGS: ${{ needs.docker-publish-base.outputs.image-tags }}
          ANNOTATIONS: ${{ needs.docker-publish-base.outputs.image-annotations }}
        # The readarray part is used to make sure the quoting and special characters are preserved on expansion (e.g. spaces)
        # The final command becomes `docker buildx imagetools create --annotation 'index:foo=1' --annotation 'index:bar=2' ... -t tag1 -t tag2 ... <IMG>@sha256:<sha256>`
        run: |
          set -x
          readarray -t lines <<< "$ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done
          for image in $IMAGES; do
            readarray -t lines < <(grep "^${image}:" <<< "$TAGS"); tags=(); for line in "${lines[@]}"; do tags+=(-t "$line"); done
            docker buildx imagetools create \
              "${annotations[@]}" \
              "${tags[@]}" \
              "${image}@${DIGEST}"
          done

      # Now that we've modified the manifest, we need to attest it again.
      # Note we only generate an attestation for GHCR.
      - name: Export manifest digest
        id: manifest-digest
        env:
          IMAGE: ${{ env.UV_GHCR_IMAGE }}
          VERSION: ${{ needs.docker-publish-base.outputs.image-version }}
        # To sign the manifest, we need it's digest. Unfortunately "docker
        # buildx imagetools create" does not (yet) have a clean way of sharing
        # the digest of the manifest it creates (see docker/buildx#2407), so
        # we use a separate command to retrieve it.
        # imagetools inspect [TAG] --format '{{json .Manifest}}' gives us
        # the machine readable JSON description of the manifest, and the
        # jq command extracts the digest from this. The digest is then
        # sent to the Github step output file for sharing with other steps.
        run: |
          digest="$(
            docker buildx imagetools inspect \
              "${IMAGE}:${VERSION}" \
              --format '{{json .Manifest}}' \
            | jq -r '.digest'
          )"
          echo "digest=${digest}" >> "$GITHUB_OUTPUT"

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.UV_GHCR_IMAGE }}
          subject-digest: ${{ steps.manifest-digest.outputs.digest }}
build-release-binaries matrix perms .github/workflows/build-release-binaries.yml
Triggers
workflow_call
Runs on
depot-ubuntu-24.04-4, depot-macos-14, depot-macos-14, ${{ matrix.platform.runner }}, depot-ubuntu-latest-4, depot-ubuntu-24.04-8, depot-ubuntu-latest-4, depot-ubuntu-24.04-4, depot-ubuntu-latest-4, depot-ubuntu-24.04-4, depot-ubuntu-24.04-8, ubuntu-slim
Jobs
sdist, macos-x86_64, macos-aarch64, windows, linux, linux-arm, linux-s390x, linux-powerpc, linux-riscv64, musllinux, musllinux-cross, check-wheels
Matrix
include, include.cc, include.target, platform, platform.arch, platform.manylinux, platform.maturin_docker_options, platform.runner, platform.target, target→ -e JEMALLOC_SYS_WITH_LG_PAGE=16, 217, 228, aarch64, aarch64-pc-windows-msvc, aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl, arm, arm-unknown-linux-musleabihf, arm64, armv7, armv7-unknown-linux-gnueabihf, armv7-unknown-linux-musleabihf, auto, gcc, gcc -m32, github-windows-11-aarch64-8, github-windows-2025-x86_64-8, i686-pc-windows-msvc, i686-unknown-linux-gnu, i686-unknown-linux-musl, powerpc64le-unknown-linux-gnu, ppc64le, riscv64, riscv64gc-unknown-linux-gnu, riscv64gc-unknown-linux-musl, s390x, s390x-unknown-linux-gnu, x64, x86, x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl
Actions
PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, uraimo/run-on-arch-action, PyO3/maturin-action, uraimo/run-on-arch-action, PyO3/maturin-action, uraimo/run-on-arch-action, PyO3/maturin-action, uraimo/run-on-arch-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, uraimo/run-on-arch-action, PyO3/maturin-action, uraimo/run-on-arch-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, uraimo/run-on-arch-action, uraimo/run-on-arch-action, PyO3/maturin-action, uraimo/run-on-arch-action, uraimo/run-on-arch-action, astral-sh/setup-uv
Commands
  • python scripts/transform_readme.py --target pypi
  • # We can't use `--find-links` here, since we need maturin, which means no `--no-index`, and without that option # we run the risk that pip pull uv from PyPI instead. pip install dist/${PACKAGE_NAME}-*.tar.gz --force-reinstall ${MODULE_NAME} --help python -m ${MODULE_NAME} --help uvx --help
  • pip install crates/uv-build/dist/${PACKAGE_NAME}_build-*.tar.gz --force-reinstall ${MODULE_NAME}-build --help python -m ${MODULE_NAME}_build --help
  • python scripts/transform_readme.py --target pypi
  • scripts/install-cargo-extensions.sh
  • TARGET=x86_64-apple-darwin ARCHIVE_NAME=uv-$TARGET ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz mkdir -p $ARCHIVE_NAME cp target/$TARGET/release/uv $ARCHIVE_NAME/uv cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx tar czvf $ARCHIVE_FILE $ARCHIVE_NAME shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
  • python scripts/transform_readme.py --target pypi
  • scripts/install-cargo-extensions.sh
View raw YAML
# Build uv on all platforms.
#
# Generates both wheels (for PyPI) and archived binaries (for GitHub releases).
#
# Called from:
# - .github/workflows/ci.yml (when release-relevant files change)
# - .github/workflows/release.yml (as a local artifacts job within `cargo-dist`)
name: "Build release binaries"

on:
  workflow_call:
    inputs:
      plan:
        required: false
        type: string

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  PACKAGE_NAME: uv
  MODULE_NAME: uv
  PYTHON_VERSION: "3.11"
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

permissions: {}

jobs:
  sdist:
    name: sdist
    runs-on: depot-ubuntu-24.04-4
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}

      # uv
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build sdist"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          command: sdist
          args: --out dist
      - name: "Test sdist"
        run: |
          # We can't use `--find-links` here, since we need maturin, which means no `--no-index`, and without that option
          # we run the risk that pip pull uv from PyPI instead.
          pip install dist/${PACKAGE_NAME}-*.tar.gz --force-reinstall
          ${MODULE_NAME} --help
          python -m ${MODULE_NAME} --help
          uvx --help
      - name: "Upload sdist"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-sdist
          path: dist

      # uv-build
      - name: "Build sdist uv-build"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          command: sdist
          args: --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
      - name: "Test sdist uv-build"
        run: |
          pip install crates/uv-build/dist/${PACKAGE_NAME}_build-*.tar.gz --force-reinstall
          ${MODULE_NAME}-build --help
          python -m ${MODULE_NAME}_build --help
      - name: "Upload sdist uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-sdist
          path: crates/uv-build/dist

  macos-x86_64:
    name: x86_64-apple-darwin
    runs-on: depot-macos-14
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Install cargo extensions"
        shell: bash
        run: scripts/install-cargo-extensions.sh

      # uv
      - name: "Build wheels - x86_64"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: x86_64
          args: --release --locked --out dist --features self-update --compatibility pypi
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-macos-x86_64
          path: dist
      - name: "Archive binary"
        run: |
          TARGET=x86_64-apple-darwin
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-macos-x86_64
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels uv-build - x86_64"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: x86_64
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-macos-x86_64
          path: crates/uv-build/dist

  macos-aarch64:
    name: aarch64-apple-darwin
    runs-on: depot-macos-14
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: arm64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Install cargo extensions"
        shell: bash
        run: scripts/install-cargo-extensions.sh

      # uv
      - name: "Build wheels - aarch64"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: aarch64
          manylinux: 2_17
          args: --release --locked --out dist --features self-update --compatibility pypi
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - name: "Test wheel - aarch64"
        run: |
          pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
          ${MODULE_NAME} --help
          python -m ${MODULE_NAME} --help
          uvx --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-aarch64-apple-darwin
          path: dist
      - name: "Archive binary"
        run: |
          TARGET=aarch64-apple-darwin
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-aarch64-apple-darwin
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels uv-build - aarch64"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: aarch64
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - name: "Test wheel - aarch64"
        run: |
          pip install ${PACKAGE_NAME}_build --no-index --find-links crates/uv-build/dist --force-reinstall
          ${MODULE_NAME}-build --help
          python -m ${MODULE_NAME}_build --help
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-aarch64-apple-darwin
          path: crates/uv-build/dist

  windows:
    name: ${{ matrix.platform.target }}
    runs-on: ${{ matrix.platform.runner }}
    strategy:
      matrix:
        platform:
          - target: x86_64-pc-windows-msvc
            arch: x64
            runner: github-windows-2025-x86_64-8
          - target: i686-pc-windows-msvc
            arch: x86
            runner: github-windows-2025-x86_64-8
          - target: aarch64-pc-windows-msvc
            arch: arm64
            runner: github-windows-11-aarch64-8
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: ${{ matrix.platform.arch }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Install cargo extensions"
        shell: bash
        run: scripts/install-cargo-extensions.sh
      - name: "Install NASM"
        # NASM is required for x86/x86-64 Windows targets by aws-lc-sys.
        # On aarch64-pc-windows-msvc, it uses clang-cl instead.
        # See: https://aws.github.io/aws-lc-rs/requirements/windows.html#build-requirements
        if: contains(matrix.platform.target, 'x86') || contains(matrix.platform.target, 'i686')
        run: |
          winget install NASM.NASM --accept-source-agreements --accept-package-agreements
          echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Append
      # uv
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          args: --release --locked --out dist --features self-update,windows-gui-bin --compatibility pypi
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.cmd
          # Disable prebuilt NASM objects so we always compile assembly from source.
          AWS_LC_SYS_PREBUILT_NASM: "0"
      - name: "Test wheel"
        shell: bash
        run: |
          pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
          ${MODULE_NAME} --help
          python -m ${MODULE_NAME} --help
          uvx --help
          uvw --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_FILE=uv-${PLATFORM_TARGET}.zip
          7z a $ARCHIVE_FILE ./target/${PLATFORM_TARGET}/release/uv.exe
          7z a $ARCHIVE_FILE ./target/${PLATFORM_TARGET}/release/uvx.exe
          7z a $ARCHIVE_FILE ./target/${PLATFORM_TARGET}/release/uvw.exe
          sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
        env:
          PLATFORM_TARGET: ${{ matrix.platform.target }}
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.zip
            *.sha256

      # uv-build
      - name: "Build wheels uv-build"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.cmd
      - name: "Test wheel uv-build"
        shell: bash
        run: |
          pip install ${PACKAGE_NAME}_build --no-index --find-links crates/uv-build/dist --force-reinstall
          ${MODULE_NAME}-build --help
          python -m ${MODULE_NAME}_build --help
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-${{ matrix.platform.target }}
          path: crates/uv-build/dist

  linux:
    name: ${{ matrix.target }}
    runs-on: depot-ubuntu-latest-4
    strategy:
      matrix:
        include:
          - { target: "i686-unknown-linux-gnu", cc: "gcc -m32" }
          - { target: "x86_64-unknown-linux-gnu", cc: "gcc" }
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi

      # uv
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.target }}
          # Generally, we try to build in a target docker container. In this case however, a
          # 32-bit compiler runs out of memory (4GB memory limit for 32-bit), so we cross compile
          # from 64-bit version of the container, breaking the pattern from other builds.
          container: quay.io/pypa/manylinux2014
          manylinux: 2_17
          docker-options: -e CARGO
          args: --release --locked --out dist --features self-update --compatibility pypi
          before-script-linux: |
            # Install the 32-bit cross target on 64-bit (noop if we're already on 64-bit)
            rustup target add ${{ matrix.target }}
            # If we're running on rhel centos, install needed packages.
            if command -v yum &> /dev/null; then
                yum update -y && yum install -y pkgconfig libatomic

                # Install cross build requirements
                if [[ "${{ matrix.target }}" == "i686-unknown-linux-gnu" ]]; then
                  yum install -y glibc-devel.i686 libstdc++-devel.i686 libatomic.i686
                fi

                # Symlink libatomic so the linker can find it with -latomic.
                if [[ -f "/usr/lib/libatomic.so.1" && ! -f "/usr/lib/libatomic.so" ]]; then
                    ln -s /usr/lib/libatomic.so.1 /usr/lib/libatomic.so
                fi
            else
                # If we're running on debian-based system.
                apt update -y && apt-get install -y pkg-config
            fi
            # Install cargo extensions as a static musl binary so it runs in any container.
            scripts/install-cargo-extensions.sh
        env:
          CC: ${{ matrix.cc }}
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - name: "Test wheel"
        if: ${{ startsWith(matrix.target, 'x86_64') }}
        run: |
          pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
          ${MODULE_NAME} --help
          python -m ${MODULE_NAME} --help
          uvx --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-${{ matrix.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
        env:
          TARGET: ${{ matrix.target }}
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-${{ matrix.target }}
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels uv-build"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.target }}
          manylinux: 2_17
          docker-options: -e CARGO
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - name: "Test wheel uv-build"
        if: ${{ startsWith(matrix.target, 'x86_64') }}
        run: |
          pip install ${PACKAGE_NAME}_build --no-index --find-links crates/uv-build/dist --force-reinstall
          ${MODULE_NAME}-build --help
          python -m ${MODULE_NAME}_build --help
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-${{ matrix.target }}
          path: crates/uv-build/dist

  linux-arm:
    name: ${{ matrix.platform.target }}
    runs-on: depot-ubuntu-24.04-8
    timeout-minutes: 30
    strategy:
      matrix:
        platform:
          - target: aarch64-unknown-linux-gnu
            arch: aarch64
            # see https://github.com/astral-sh/ruff/issues/3791
            # and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
            # Build fails with 2_17 container: https://github.com/astral-sh/uv/actions/runs/20850906093/job/59905482208?pr=17358
            manylinux: 2_28
          - target: armv7-unknown-linux-gnueabihf
            arch: armv7
            manylinux: 2_17
          - target: arm-unknown-linux-musleabihf
            arch: arm
            # Special case: armv6l is linux_armv6l, no manylinux or musllinux.
            # "auto" instead of "off" to get the cross container
            manylinux: auto

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi

      # uv
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: ${{ matrix.platform.manylinux }}
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --features self-update --compatibility pypi
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel"
        with:
          arch: ${{ matrix.platform.arch == 'arm' && 'armv6' || matrix.platform.arch }}
          distro: ${{ matrix.platform.arch == 'arm' && 'bullseye' || 'ubuntu20.04' }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
            pip3 install -U pip
          run: |
            pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
            ${MODULE_NAME} --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # python -m ${MODULE_NAME} --help
            uvx --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
        env:
          TARGET: ${{ matrix.platform.target }}
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels uv-build"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: ${{ matrix.platform.manylinux }}
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel uv-build"
        with:
          arch: ${{ matrix.platform.arch == 'arm' && 'armv6' || matrix.platform.arch }}
          distro: ${{ matrix.platform.arch == 'arm' && 'bullseye' || 'ubuntu20.04' }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
            pip3 install -U pip
          run: |
            pip install ${PACKAGE_NAME}_build --no-index --find-links crates/uv-build/dist --force-reinstall
            ${MODULE_NAME}-build --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # python -m ${MODULE_NAME}_build --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-${{ matrix.platform.target }}
          path: crates/uv-build/dist

  # Like `linux-arm`.
  linux-s390x:
    name: ${{ matrix.platform.target }}
    timeout-minutes: 30
    runs-on: depot-ubuntu-latest-4
    strategy:
      matrix:
        platform:
          - target: s390x-unknown-linux-gnu
            arch: s390x

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi

      # uv
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: 2_17
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --features self-update --compatibility pypi
          rust-toolchain: ${{ matrix.platform.toolchain || null }}
          before-script-linux: |
            scripts/install-cargo-extensions.sh
            # Install the s390x cross target on x86_64
            rustup target add ${{ matrix.platform.target }}
            apt-get update && apt-get install -y gcc-s390x-linux-gnu binutils-s390x-linux-gnu
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh

      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel"
        with:
          arch: ${{ matrix.platform.arch }}
          distro: ubuntu20.04
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
            pip3 install -U pip
          run: |
            pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
            ${MODULE_NAME} --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # python -m ${MODULE_NAME} --help
            uvx --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
        env:
          TARGET: ${{ matrix.platform.target }}
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels uv-build"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: 2_17
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel uv-build"
        with:
          arch: ${{ matrix.platform.arch }}
          distro: ubuntu20.04
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
            pip3 install -U pip
          run: |
            pip install ${PACKAGE_NAME}-build --no-index --find-links crates/uv-build/dist --force-reinstall
            ${MODULE_NAME}-build --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # python -m ${MODULE_NAME}-build --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-${{ matrix.platform.target }}
          path: crates/uv-build/dist

  # Like `linux-arm`, but install the `gcc-powerpc64-linux-gnu` package.
  linux-powerpc:
    name: ${{ matrix.platform.target }}
    runs-on: depot-ubuntu-24.04-4
    strategy:
      matrix:
        platform:
          - target: powerpc64le-unknown-linux-gnu
            arch: ppc64le
            # see https://github.com/astral-sh/uv/issues/6528
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi

      # uv
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: 2_17
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --features self-update --compatibility pypi
          before-script-linux: |
            if command -v yum &> /dev/null; then
                yum update -y
                yum -y install epel-release
                yum repolist
                yum install -y gcc-powerpc64-linux-gnu
            fi
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      # TODO(charlie): Re-enable testing for PPC wheels.
      # - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
      #   name: "Test wheel"
      #   with:
      #     arch: ${{ matrix.platform.arch }}
      #     distro: ubuntu20.04
      #     install: |
      #       apt-get update
      #       apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
      #       pip3 install -U pip
      #     run: |
      #       pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
      #       ${MODULE_NAME} --help
      #       #(konsti) TODO: Enable this test on all platforms,currently `find_uv_bin` is failingto discover uv here.
      #       # python -m ${MODULE_NAME} --helppython -m ${MODULE_NAME} --help
      #       uvx --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
        env:
          TARGET: ${{ matrix.platform.target }}
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels uv-build"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: 2_17
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
          before-script-linux: |
            if command -v yum &> /dev/null; then
                yum update -y
                yum -y install epel-release
                yum repolist
                yum install -y gcc-powerpc64-linux-gnu
            fi
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      # TODO(charlie): Re-enable testing for PPC wheels.
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-${{ matrix.platform.target }}
          path: crates/uv-build/dist

  # Like `linux-arm`.
  linux-riscv64:
    name: ${{ matrix.platform.target }}
    timeout-minutes: 30
    runs-on: depot-ubuntu-latest-4
    strategy:
      matrix:
        platform:
          - target: riscv64gc-unknown-linux-gnu
            arch: riscv64

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi

      # uv
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: 2_31
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --features self-update --compatibility pypi
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel"
        with:
          arch: ${{ matrix.platform.arch }}
          distro: ubuntu20.04
          githubToken: ${{ github.token }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
            pip3 install -U pip
          run: |
            pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
            ${MODULE_NAME} --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # python -m ${MODULE_NAME} --help
            uvx --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
        env:
          TARGET: ${{ matrix.platform.target }}
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels uv-build"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: 2_31
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel uv-build"
        with:
          arch: ${{ matrix.platform.arch }}
          distro: ubuntu20.04
          githubToken: ${{ github.token }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
            pip3 install -U pip
          run: |
            pip install ${PACKAGE_NAME}-build --no-index --find-links crates/uv-build/dist --force-reinstall
            ${MODULE_NAME}-build --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # python -m ${MODULE_NAME}-build --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-${{ matrix.platform.target }}
          path: crates/uv-build/dist

  musllinux:
    name: ${{ matrix.target }}
    runs-on: depot-ubuntu-24.04-4
    strategy:
      matrix:
        target:
          - x86_64-unknown-linux-musl
          - i686-unknown-linux-musl
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi

      # uv
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.target }}
          manylinux: musllinux_1_1
          docker-options: -e CARGO
          args: --release --locked --out dist --features self-update --compatibility pypi
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - name: "Test wheel"
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: |
          docker run --rm -v ${{ github.workspace }}:/io -w /io --env MODULE_NAME --env PACKAGE_NAME alpine:3.12 sh -c "
            apk add python3 py3-pip;
            python3 -m venv .venv;
            .venv/bin/pip install --upgrade pip;
            .venv/bin/pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall;
            .venv/bin/${MODULE_NAME} --help;
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # .venv/bin/python -m ${MODULE_NAME} --help;
            .venv/bin/uvx --help;
          "
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-${{ matrix.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
        env:
          TARGET: ${{ matrix.target }}
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-${{ matrix.target }}
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels uv-build"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.target }}
          manylinux: musllinux_1_1
          docker-options: -e CARGO
          args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - name: "Test wheel uv-build"
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: |
          docker run --rm -v ${{ github.workspace }}:/io -w /io --env MODULE_NAME --env PACKAGE_NAME alpine:3.12 sh -c "
            apk add python3 py3-pip;
            python3 -m venv .venv;
            .venv/bin/pip install --upgrade pip;
            .venv/bin/pip install ${PACKAGE_NAME}-build --no-index --find-links crates/uv-build/dist --force-reinstall;
            .venv/bin/${MODULE_NAME}-build --help;
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # .venv/bin/python -m ${MODULE_NAME}_build --help;
          "
      - name: "Upload wheels uv-build"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-${{ matrix.target }}
          path: crates/uv-build/dist

  musllinux-cross:
    name: ${{ matrix.platform.target }}
    runs-on: depot-ubuntu-24.04-8
    env:
      CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-C target-feature=+crt-static"
    strategy:
      matrix:
        platform:
          - target: aarch64-unknown-linux-musl
            arch: aarch64
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
          - target: armv7-unknown-linux-musleabihf
            arch: armv7
          - target: riscv64gc-unknown-linux-musl
            arch: riscv64
      fail-fast: false

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi

      # uv
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: musllinux_1_1
          # Tag the musl builds as manylinux 2_17 fallback cause the aarch64 build only support 2_28
          args: --release --locked --out dist --features self-update --compatibility ${{ matrix.platform.arch == 'riscv64' && '2_31' || '2_17'}} --compatibility pypi
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          rust-toolchain: ${{ matrix.platform.toolchain || null }}
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel"
        with:
          arch: ${{ matrix.platform.arch }}
          distro: alpine_latest
          install: |
            apk add python3
          run: |
            python -m venv .venv
            .venv/bin/pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
            .venv/bin/${MODULE_NAME} --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # .venv/bin/python -m ${MODULE_NAME} --help
            .venv/bin/uvx --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel (manylinux)"
        if: matrix.platform.arch == 'aarch64'
        with:
          arch: aarch64
          distro: ubuntu20.04
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
            pip3 install -U pip
          run: |
            pip install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall
            ${MODULE_NAME} --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # python -m ${MODULE_NAME} --help
            uvx --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/$PROFILE/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/$PROFILE/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
        env:
          TARGET: ${{ matrix.platform.target }}
          PROFILE: ${{ matrix.platform.arch == 'ppc64le' && 'release-no-lto' || 'release' }}
      - name: "Upload binary"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

      # uv-build
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.12.6
          target: ${{ matrix.platform.target }}
          manylinux: musllinux_1_1
          args: --profile minimal-size --locked ${{ matrix.platform.arch == 'aarch64' && '--compatibility 2_17' || ''}} --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi
          docker-options: -e CARGO ${{ matrix.platform.maturin_docker_options }}
          rust-toolchain: ${{ matrix.platform.toolchain || null }}
          before-script-linux: |
            scripts/install-cargo-extensions.sh
        env:
          CARGO: ${{ github.workspace }}/scripts/cargo.sh
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel"
        with:
          arch: ${{ matrix.platform.arch }}
          distro: alpine_latest
          install: |
            apk add python3
          run: |
            python -m venv .venv
            .venv/bin/pip install ${PACKAGE_NAME}-build --no-index --find-links crates/uv-build/dist --force-reinstall
            .venv/bin/${MODULE_NAME}-build --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # .venv/bin/python -m ${MODULE_NAME}_build --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        name: "Test wheel (manylinux)"
        if: matrix.platform.arch == 'aarch64'
        with:
          arch: aarch64
          distro: ubuntu20.04
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip python-is-python3
            pip3 install -U pip
          run: |
            pip install ${PACKAGE_NAME}-build --no-index --find-links crates/uv-build/dist --force-reinstall
            ${MODULE_NAME}-build --help
            # TODO(konsti): Enable this test on all platforms, currently `find_uv_bin` is failing to discover uv here.
            # python -m ${MODULE_NAME}_build --help
          env: |
            PACKAGE_NAME: ${{ env.PACKAGE_NAME }}
            MODULE_NAME: ${{ env.MODULE_NAME }}
      - name: "Upload wheels"
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: wheels_uv_build-${{ matrix.platform.target }}
          path: crates/uv-build/dist

  check-wheels:
    name: "Check wheel contents"
    runs-on: ubuntu-slim
    needs:
      - macos-x86_64
      - macos-aarch64
      - windows
      - linux
      - linux-arm
      - linux-s390x
      - linux-powerpc
      - linux-riscv64
      - musllinux
      - musllinux-cross
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          pattern: wheels_*-*
          path: wheels
          merge-multiple: true
      - name: "Check wheel contents"
        run: uv run --no-project scripts/check_uv_wheel_contents.py wheels/*
check-docs perms .github/workflows/check-docs.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
docs
Actions
astral-sh/setup-uv, Swatinem/rust-cache
Commands
  • cargo dev generate-options-reference cargo dev generate-cli-reference cargo dev generate-env-vars-reference
  • uv run --only-group docs mkdocs build --strict -f mkdocs.yml
View raw YAML
on:
  workflow_call:

permissions: {}

jobs:
  docs:
    timeout-minutes: 10
    name: "mkdocs"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Generate reference documentation"
        run: |
          cargo dev generate-options-reference
          cargo dev generate-cli-reference
          cargo dev generate-env-vars-reference

      - name: "Build docs"
        run: uv run --only-group docs mkdocs build --strict -f mkdocs.yml
check-fmt perms .github/workflows/check-fmt.yml
Triggers
workflow_call
Runs on
ubuntu-latest, ubuntu-slim, ubuntu-slim
Jobs
rust, prettier, python
Actions
astral-sh/setup-uv
Commands
  • rustup component add rustfmt
  • cargo fmt --all --check
  • npx prettier --check .
  • uvx ruff format --diff .
View raw YAML
on:
  workflow_call:

permissions: {}

jobs:
  rust:
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install Rustfmt"
        run: rustup component add rustfmt
      - run: cargo fmt --all --check

  prettier:
    timeout-minutes: 10
    runs-on: ubuntu-slim
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - run: npx prettier --check .

  python:
    timeout-minutes: 10
    runs-on: ubuntu-slim
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - run: uvx ruff format --diff .
check-generated-files perms .github/workflows/check-generated-files.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
cargo-dev-generate-all
Actions
Swatinem/rust-cache
Commands
  • cargo dev generate-all --mode dry-run
  • cargo dev generate-sysconfig-metadata --mode check
  • cargo dev generate-json-schema --mode check
View raw YAML
on:
  workflow_call:
    inputs:
      schema-changed:
        required: true
        type: string
      save-rust-cache:
        required: false
        type: string
        default: "true"

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  cargo-dev-generate-all:
    timeout-minutes: 10
    runs-on: ubuntu-latest
    name: "cargo dev generate-all"
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}
      - name: "Generate all"
        run: cargo dev generate-all --mode dry-run
      - name: "Check sysconfig mappings"
        run: cargo dev generate-sysconfig-metadata --mode check
      - name: "Check JSON schema"
        if: ${{ inputs.schema-changed == 'true' }}
        run: cargo dev generate-json-schema --mode check
check-lint perms .github/workflows/check-lint.yml
Triggers
workflow_call
Runs on
ubuntu-slim, ubuntu-slim, ubuntu-slim, ubuntu-slim, ubuntu-slim, ubuntu-latest, windows-latest, ubuntu-latest, ubuntu-slim
Jobs
ruff, ty, shellcheck, validate-pyproject, readme, clippy-ubuntu, clippy-windows, shear, typos
Actions
astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv, Swatinem/rust-cache, EmbarkStudios/cargo-deny-action, Swatinem/rust-cache, taiki-e/install-action, crate-ci/typos
Commands
  • uvx ruff check .
  • uvx ty check python/uv uvx \ --directory crates/uv-python \ --with-requirements fetch-download-metadata.py \ ty check --python-version 3.13 fetch-download-metadata.py
  • # renovate: datasource=github-releases depName=koalaman/shellcheck SHELLCHECK_VERSION="v0.11.0" curl -sSL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar -xJf - sudo mv "shellcheck-${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/
  • find . -name '*.sh' -type f | xargs shellcheck --shell bash --severity style
  • uvx --from 'validate-pyproject[all,store]' validate-pyproject pyproject.toml
  • python scripts/transform_readme.py --target pypi
  • rustup component add clippy
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
View raw YAML
on:
  workflow_call:
    inputs:
      code-changed:
        required: true
        type: string
      save-rust-cache:
        required: false
        type: string
        default: "true"

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  ruff:
    timeout-minutes: 10
    runs-on: ubuntu-slim
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - run: uvx ruff check .

  ty:
    timeout-minutes: 10
    runs-on: ubuntu-slim
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - run: |
          uvx ty check python/uv
          uvx \
            --directory crates/uv-python \
            --with-requirements fetch-download-metadata.py \
            ty check --python-version 3.13 fetch-download-metadata.py

  shellcheck:
    timeout-minutes: 10
    runs-on: ubuntu-slim
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install shellcheck"
        run: |
          # renovate: datasource=github-releases depName=koalaman/shellcheck
          SHELLCHECK_VERSION="v0.11.0"
          curl -sSL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar -xJf -
          sudo mv "shellcheck-${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/
      - name: "Run shellcheck"
        run: find . -name '*.sh' -type f | xargs shellcheck --shell bash --severity style

  validate-pyproject:
    timeout-minutes: 10
    runs-on: ubuntu-slim
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - run: uvx --from 'validate-pyproject[all,store]' validate-pyproject pyproject.toml

  readme:
    timeout-minutes: 10
    runs-on: ubuntu-slim
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: 3.12
      - run: python scripts/transform_readme.py --target pypi

  clippy-ubuntu:
    name: "clippy on linux"
    timeout-minutes: 10
    if: ${{ inputs.code-changed == 'true' || github.ref == 'refs/heads/main' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}
      - name: "Check uv_build dependencies"
        uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15
        with:
          command: check bans
          manifest-path: crates/uv-build/Cargo.toml
      - name: "Install Rust toolchain"
        run: rustup component add clippy
      - name: "Clippy"
        run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings

  clippy-windows:
    name: "clippy on windows"
    timeout-minutes: 15
    if: ${{ inputs.code-changed == 'true' || github.ref == 'refs/heads/main' }}
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup Dev Drive
        run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1

      # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
      - name: Copy Git Repo to Dev Drive
        run: |
          Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: ${{ env.UV_WORKSPACE }}
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Install Rust toolchain"
        run: rustup component add clippy

      - name: "Clippy"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings

  shear:
    name: "cargo shear"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install cargo shear"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-shear
      - run: cargo shear --deny-warnings

  typos:
    runs-on: ubuntu-slim
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: crate-ci/typos@631208b7aac2daa8b707f55e7331f9112b0e062d # v1.44.0
check-publish perms .github/workflows/check-publish.yml
Triggers
workflow_call
Runs on
depot-ubuntu-24.04-8
Jobs
cargo-publish-dry-run
Actions
Swatinem/rust-cache
Commands
  • cargo publish --workspace --dry-run
View raw YAML
on:
  workflow_call:

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  cargo-publish-dry-run:
    timeout-minutes: 20
    runs-on: depot-ubuntu-24.04-8
    name: "cargo publish dry-run"
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "cargo publish dry-run"
        run: cargo publish --workspace --dry-run
check-release .github/workflows/check-release.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
dist-plan
Commands
  • curl --proto '=https' --tlsv1.2 -LsSf "https://github.com/axodotdev/cargo-dist/releases/download/v${CARGO_DIST_VERSION}/cargo-dist-x86_64-unknown-linux-gnu.tar.xz" -o /tmp/cargo-dist.tar.xz echo "${CARGO_DIST_CHECKSUM} /tmp/cargo-dist.tar.xz" | sha256sum -c - tar -xf /tmp/cargo-dist.tar.xz -C /tmp install /tmp/cargo-dist-x86_64-unknown-linux-gnu/dist ~/.cargo/bin/
  • dist plan --output-format=json > plan-dist-manifest.json echo "dist plan completed successfully" cat plan-dist-manifest.json
View raw YAML
on:
  workflow_call:

env:
  CARGO_DIST_VERSION: "0.31.0"
  CARGO_DIST_CHECKSUM: "cd355dab0b4c02fb59038fef87655550021d07f45f1d82f947a34ef98560abb8"

jobs:
  dist-plan:
    name: "dist plan"
    runs-on: ubuntu-latest
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
          submodules: recursive

      - name: Install dist
        shell: bash
        run: |
          curl --proto '=https' --tlsv1.2 -LsSf "https://github.com/axodotdev/cargo-dist/releases/download/v${CARGO_DIST_VERSION}/cargo-dist-x86_64-unknown-linux-gnu.tar.xz" -o /tmp/cargo-dist.tar.xz
          echo "${CARGO_DIST_CHECKSUM}  /tmp/cargo-dist.tar.xz" | sha256sum -c -
          tar -xf /tmp/cargo-dist.tar.xz -C /tmp
          install /tmp/cargo-dist-x86_64-unknown-linux-gnu/dist ~/.cargo/bin/

      - name: Run dist plan
        run: |
          dist plan --output-format=json > plan-dist-manifest.json
          echo "dist plan completed successfully"
          cat plan-dist-manifest.json
check-zizmor perms .github/workflows/check-zizmor.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
zizmor
Actions
zizmorcore/zizmor-action
View raw YAML
on:
  workflow_call:

permissions: {}

jobs:
  zizmor:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
ci perms .github/workflows/ci.yml
Triggers
push, pull_request, workflow_dispatch
Runs on
depot-ubuntu-24.04, ubuntu-latest, ubuntu-slim
Jobs
plan, check-fmt, check-lint, check-docs, check-zizmor, check-publish, check-release, check-generated-files, test, test-windows-trampolines, build-dev-binaries, test-smoke, test-integration, test-system, test-ecosystem, build-release-binaries, build-docker, bench, test-publish, required-checks-passed
Actions
pypa/gh-action-pypi-publish, digital-blueprint/gitlab-pipeline-trigger-action
Commands
  • [[ "$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_PUBLISH_LABEL" == "true" ]] && has_publish_label=1 [[ "$HAS_BUILD_SKIP_LABEL" == "true" ]] && has_build_skip_label=1 [[ "$HAS_BUILD_SKIP_DOCKER_LABEL" == "true" ]] && has_build_skip_docker_label=1 [[ "$HAS_BUILD_SKIP_RELEASE_LABEL" == "true" ]] && has_build_skip_release_label=1 [[ "$HAS_BUILD_RELEASE_LABEL" == "true" ]] && has_build_release_label=1 [[ "$HAS_BUILD_PUSH_DOCKER_LABEL" == "true" ]] && has_build_push_docker_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" || "$file" == ".github/workflows/release.yml" ]] && release_workflow_changed=1 [[ "$file" == "scripts/check_uv_wheel_contents.py" || "$file" == "scripts/patch-dist-manifest-checksums.py" ]] && release_build_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/ || "$file" == "crates/uv/src/commands/publish.rs" ]] && 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/bench.yml" ]] && bench_workflow_changed=1 [[ "$file" == ".github/workflows/test-integration.yml" || "$file" =~ ^test/integration/ || "$file" == "scripts/check_registry.py" || "$file" == "scripts/check_cache_compat.py" || "$file" == "scripts/registries-test.py" ]] && integration_changed=1 [[ "$file" == ".github/workflows/test-system.yml" ]] && system_workflow_changed=1 [[ "$file" == "scripts/check_system_python.py" || "$file" == "scripts/check_embedded_python.py" ]] && system_test_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 || $has_publish_label || $has_extended_label || $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 || $bench_workflow_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 || $integration_changed ]] && test_integration=1 [[ $has_system_label || $has_extended_label || $on_main_branch || $system_workflow_changed || $system_test_changed ]] && test_system=1 [[ $has_macos_label || $has_extended_label || $on_main_branch || $build_release_binaries ]] && test_macos=1 [[ ! $has_build_skip_label && ! $has_build_skip_docker_label && ($docker_build_changed || $has_build_push_docker_label) ]] && build_docker=1 [[ $has_build_push_docker_label ]] && push_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" out push_docker "$push_docker" } >> "$GITHUB_OUTPUT"
  • chmod +x ./uv
  • # 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 --no-workspace # 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[].filename | select(endswith(".tar.gz"))] | last' | grep -oP '(?<=astral_test_pypa_gh_action-0\.1\.)\d+(?=\.tar\.gz)') # 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
  • # 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}"
  • # `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__
  • ./uv auth login https://test.pypi.org/legacy/?astral-test-text-store --token ${UV_TEST_PUBLISH_TEXT_STORE}
  • ./uv run --no-project -p "${PYTHON_VERSION}" scripts/publish/test_publish.py --uv ./uv all
  • 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
View raw YAML
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 }}
      push-docker: ${{ steps.plan.outputs.push_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_PUBLISH_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'test:publish') }}
          HAS_BUILD_SKIP_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'build:skip') }}
          HAS_BUILD_SKIP_DOCKER_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'build:skip-docker') }}
          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') }}
          HAS_BUILD_PUSH_DOCKER_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'build:push-docker') }}
          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_PUBLISH_LABEL" == "true" ]] && has_publish_label=1
          [[ "$HAS_BUILD_SKIP_LABEL" == "true" ]] && has_build_skip_label=1
          [[ "$HAS_BUILD_SKIP_DOCKER_LABEL" == "true" ]] && has_build_skip_docker_label=1
          [[ "$HAS_BUILD_SKIP_RELEASE_LABEL" == "true" ]] && has_build_skip_release_label=1
          [[ "$HAS_BUILD_RELEASE_LABEL" == "true" ]] && has_build_release_label=1
          [[ "$HAS_BUILD_PUSH_DOCKER_LABEL" == "true" ]] && has_build_push_docker_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" || "$file" == ".github/workflows/release.yml" ]] && release_workflow_changed=1
            [[ "$file" == "scripts/check_uv_wheel_contents.py" || "$file" == "scripts/patch-dist-manifest-checksums.py" ]] && release_build_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/ || "$file" == "crates/uv/src/commands/publish.rs" ]] && 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/bench.yml" ]] && bench_workflow_changed=1
            [[ "$file" == ".github/workflows/test-integration.yml" || "$file" =~ ^test/integration/ || "$file" == "scripts/check_registry.py" || "$file" == "scripts/check_cache_compat.py" || "$file" == "scripts/registries-test.py" ]] && integration_changed=1
            [[ "$file" == ".github/workflows/test-system.yml" ]] && system_workflow_changed=1
            [[ "$file" == "scripts/check_system_python.py" || "$file" == "scripts/check_embedded_python.py" ]] && system_test_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 || $has_publish_label || $has_extended_label || $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 || $bench_workflow_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 || $integration_changed ]] && test_integration=1
          [[ $has_system_label || $has_extended_label || $on_main_branch || $system_workflow_changed || $system_test_changed ]] && test_system=1
          [[ $has_macos_label || $has_extended_label || $on_main_branch || $build_release_binaries ]] && test_macos=1
          [[ ! $has_build_skip_label && ! $has_build_skip_docker_label && ($docker_build_changed || $has_build_push_docker_label) ]] && build_docker=1
          [[ $has_build_push_docker_label ]] && push_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"
            out push_docker             "$push_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
    with:
      push-dev: ${{ needs.plan.outputs.push-docker == 'true' }}
    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:
      name: uv-test-publish
      deployment: false
    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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.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"
        shell: bash -eo pipefail {0}
        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 --no-workspace
          # 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[].filename | select(endswith(".tar.gz"))] | last' | grep -oP '(?<=astral_test_pypa_gh_action-0\.1\.)\d+(?=\.tar\.gz)')
          # 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@c59b56e9d2688ab42c1304322ac8831a4ef6f7d2 # v1.4.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 --no-project -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) }}
publish-crates .github/workflows/publish-crates.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
crates-publish-uv
Actions
rust-lang/crates-io-auth-action
Commands
  • cargo publish --workspace --no-verify
View raw YAML
# Publish a release to crates.io.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job
# within `cargo-dist`.
name: "Publish to crates.io"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

jobs:
  crates-publish-uv:
    name: Upload uv to crates.io
    runs-on: ubuntu-latest
    environment:
      name: release
      deployment: false
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4
        id: auth
      - name: Publish workspace crates
        # Note `--no-verify` is safe because we do a publish dry-run elsewhere in CI
        run: cargo publish --workspace --no-verify
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
publish-docs perms .github/workflows/publish-docs.yml
Triggers
workflow_dispatch, workflow_call
Runs on
ubuntu-latest
Jobs
mkdocs
Actions
Swatinem/rust-cache, astral-sh/setup-uv
Commands
  • cargo dev generate-options-reference cargo dev generate-cli-reference cargo dev generate-env-vars-reference
  • version="${VERSION}" # if version is missing, use 'latest' if [ -z "$version" ]; then echo "Using 'latest' as version" version="latest" fi # Use version as display name for now display_name="$version" echo "DISPLAY_NAME=$display_name" >> $GITHUB_ENV
  • version="${VERSION}" display_name="${DISPLAY_NAME}" timestamp="$(date +%s)" # create branch_display_name from display_name by replacing all # characters disallowed in git branch names with hyphens branch_display_name="$(echo "$display_name" | tr -c '[:alnum:]._' '-' | tr -s '-')" echo "BRANCH_NAME=update-docs-$branch_display_name-$timestamp" >> $GITHUB_ENV echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV
  • uv run --only-group docs mkdocs build --strict -f mkdocs.yml
  • version="${VERSION}" git clone https://${ASTRAL_DOCS_PAT}@github.com/astral-sh/docs.git astral-docs
  • rm -rf astral-docs/site/uv && mkdir -p astral-docs/site && cp -r site/uv astral-docs/site/
  • branch_name="${BRANCH_NAME}" git config user.name "astral-docs-bot" git config user.email "176161322+astral-docs-bot@users.noreply.github.com" git checkout -b $branch_name git add site/uv git commit -m "Update uv documentation for $version"
  • version="${VERSION}" display_name="${DISPLAY_NAME}" branch_name="${BRANCH_NAME}" # set the PR title pull_request_title="Update uv documentation for $display_name" # Delete any existing pull requests that are open for this version # by checking against pull_request_title because the new PR will # supersede the old one. gh pr list --state open --json title --jq '.[] | select(.title == "$pull_request_title") | .number' | \ xargs -I {} gh pr close {} # push the branch to GitHub git push origin $branch_name # create the PR gh pr create --base main --head $branch_name \ --title "$pull_request_title" \ --body "Automated documentation update for $display_name" \ --label "documentation"
View raw YAML
# Publish the uv documentation.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: mkdocs

on:
  workflow_dispatch:
    inputs:
      ref:
        description: "The commit SHA, tag, or branch to publish. Uses the default branch if not specified."
        default: ""
        type: string
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions: {}

jobs:
  mkdocs:
    environment:
      name: release
    runs-on: ubuntu-latest
    env:
      VERSION: ${{ (inputs.plan != '' && fromJson(inputs.plan).announcement_tag) || inputs.ref }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          ref: ${{ inputs.ref }}
          fetch-depth: 0
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: 3.12

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1

      - name: "Generate reference documentation"
        run: |
          cargo dev generate-options-reference
          cargo dev generate-cli-reference
          cargo dev generate-env-vars-reference

      - name: "Set docs display name"
        run: |
          version="${VERSION}"
          # if version is missing, use 'latest'
          if [ -z "$version" ]; then
            echo "Using 'latest' as version"
            version="latest"
          fi

          # Use version as display name for now
          display_name="$version"

          echo "DISPLAY_NAME=$display_name" >> $GITHUB_ENV

      - name: "Set branch name"
        run: |
          version="${VERSION}"
          display_name="${DISPLAY_NAME}"
          timestamp="$(date +%s)"

          # create branch_display_name from display_name by replacing all
          # characters disallowed in git branch names with hyphens
          branch_display_name="$(echo "$display_name" | tr -c '[:alnum:]._' '-' | tr -s '-')"

          echo "BRANCH_NAME=update-docs-$branch_display_name-$timestamp" >> $GITHUB_ENV
          echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV

      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

      - name: "Build docs"
        run: uv run --only-group docs mkdocs build --strict -f mkdocs.yml

      - name: "Clone docs repo"
        run: |
          version="${VERSION}"
          git clone https://${ASTRAL_DOCS_PAT}@github.com/astral-sh/docs.git astral-docs
        env:
          ASTRAL_DOCS_PAT: ${{ secrets.ASTRAL_DOCS_PAT }}

      - name: "Copy docs"
        run: rm -rf astral-docs/site/uv && mkdir -p astral-docs/site && cp -r site/uv astral-docs/site/

      - name: "Commit docs"
        working-directory: astral-docs
        run: |
          branch_name="${BRANCH_NAME}"

          git config user.name "astral-docs-bot"
          git config user.email "176161322+astral-docs-bot@users.noreply.github.com"

          git checkout -b $branch_name
          git add site/uv
          git commit -m "Update uv documentation for $version"

      - name: "Create Pull Request"
        working-directory: astral-docs
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
        run: |
          version="${VERSION}"
          display_name="${DISPLAY_NAME}"
          branch_name="${BRANCH_NAME}"

          # set the PR title
          pull_request_title="Update uv documentation for $display_name"

          # Delete any existing pull requests that are open for this version
          # by checking against pull_request_title because the new PR will
          # supersede the old one.
          gh pr list --state open --json title --jq '.[] | select(.title == "$pull_request_title") | .number' | \
            xargs -I {} gh pr close {}

          # push the branch to GitHub
          git push origin $branch_name

          # create the PR
          gh pr create --base main --head $branch_name \
            --title "$pull_request_title" \
            --body "Automated documentation update for $display_name" \
            --label "documentation"

      - name: "Merge Pull Request"
        if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
        working-directory: astral-docs
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
        run: |
          branch_name="${BRANCH_NAME}"

          # auto-merge the PR if the build was triggered by a release. Manual builds should be reviewed by a human.
          # give the PR a few seconds to be created before trying to auto-merge it
          sleep 10
          gh pr merge --squash $branch_name
publish-mirror perms .github/workflows/publish-mirror.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
publish-mirror
Commands
  • aws s3 cp --recursive --output table --color on \ --exclude '*' \ --include '*.zip' --include '*.zip.sha256' \ --include '*.tar.gz' --include '*.tar.gz.sha256' \ --include sha256.sum --include '*.ps1' --include '*.sh' \ --cache-control "public, max-age=31536000, immutable" \ artifacts/ \ s3://${R2_BUCKET}/github/$PROJECT/releases/download/$VERSION/
View raw YAML
# Publish uv releases to a mirror
#
# Assumed to run as a subworkflow of .github/workflows/release.yml as a custom publish job
name: publish-mirror

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions: {}

jobs:
  publish-mirror:
    runs-on: ubuntu-latest
    environment:
      name: release
      deployment: false
    env:
      VERSION: ${{ fromJson(inputs.plan).announcement_tag }}
    steps:
      - name: "Download GitHub Artifacts"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          pattern: artifacts-*
          path: artifacts
          merge-multiple: true
      - name: "Upload to R2"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.MIRROR_R2_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.MIRROR_R2_SECRET_ACCESS_KEY }}
          AWS_ENDPOINT_URL: https://${{ secrets.MIRROR_R2_CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
          AWS_DEFAULT_REGION: auto
          R2_BUCKET: ${{ secrets.MIRROR_R2_BUCKET_NAME }}
          PROJECT: uv
        run: |
          aws s3 cp --recursive --output table --color on \
            --exclude '*' \
            --include '*.zip' --include '*.zip.sha256' \
            --include '*.tar.gz' --include '*.tar.gz.sha256' \
            --include sha256.sum --include '*.ps1' --include '*.sh' \
            --cache-control "public, max-age=31536000, immutable" \
            artifacts/ \
            s3://${R2_BUCKET}/github/$PROJECT/releases/download/$VERSION/
publish-pypi .github/workflows/publish-pypi.yml
Triggers
workflow_call
Runs on
ubuntu-latest, ubuntu-latest
Jobs
pypi-publish-uv, pypi-publish-uv-build
Actions
astral-sh/setup-uv, astral-sh/setup-uv
Commands
  • uv publish -v wheels_uv/*
  • uv publish -v wheels_uv_build/*
View raw YAML
# Publish a release to PyPI.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job
# within `cargo-dist`.
name: "Publish to PyPI"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

jobs:
  pypi-publish-uv:
    name: Upload uv to PyPI
    runs-on: ubuntu-latest
    environment:
      name: release
      deployment: false
    permissions:
      id-token: write # For PyPI's trusted publishing
    steps:
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          pattern: wheels_uv-*
          path: wheels_uv
          merge-multiple: true
      - name: Publish to PyPI
        run: uv publish -v wheels_uv/*

  pypi-publish-uv-build:
    name: Upload uv-build to PyPI
    runs-on: ubuntu-latest
    environment:
      name: release
      deployment: false
    permissions:
      id-token: write # For PyPI's trusted publishing
    steps:
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          pattern: wheels_uv_build-*
          path: wheels_uv_build
          merge-multiple: true
      - name: Publish to PyPI
        run: uv publish -v wheels_uv_build/*
publish-versions perms .github/workflows/publish-versions.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
publish-versions
Actions
astral-sh/setup-uv
Commands
  • echo "BRANCH_NAME=update-versions-$VERSION-$(date +%s)" >> $GITHUB_ENV
  • git clone https://${{ secrets.ASTRAL_VERSIONS_PAT }}@github.com/astral-sh/versions.git astral-versions
  • printf '%s' "$PLAN" | uv run astral-versions/scripts/convert-cargo-dist-plan.py | uv run astral-versions/scripts/insert-versions.py --name uv
  • git config user.name "astral-versions-bot" git config user.email "176161322+astral-versions-bot@users.noreply.github.com" git checkout -b "$BRANCH_NAME" git add -A git commit -m "Update uv to version $VERSION"
  • pull_request_title="Add uv $VERSION" gh pr list --state open --json title --jq ".[] | select(.title == \"$pull_request_title\") | .number" | \ xargs -I {} gh pr close {} git push origin "$BRANCH_NAME" gh pr create --base main --head "$BRANCH_NAME" \ --title "$pull_request_title" \ --body "Automated versions update for $VERSION" \ --label "automation"
  • # Wait for PR to be created before merging sleep 10 gh pr merge --squash "$BRANCH_NAME"
View raw YAML
# Publish uv version information to the versions repository.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: publish-versions

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions: {}

jobs:
  publish-versions:
    runs-on: ubuntu-latest
    environment:
      name: release
    env:
      VERSION: ${{ fromJson(inputs.plan).announcement_tag }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Set branch name"
        run: echo "BRANCH_NAME=update-versions-$VERSION-$(date +%s)" >> $GITHUB_ENV

      - name: "Clone versions repo"
        run: git clone https://${{ secrets.ASTRAL_VERSIONS_PAT }}@github.com/astral-sh/versions.git astral-versions

      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0

      - name: "Update versions"
        env:
          PLAN: ${{ inputs.plan }}
        run: printf '%s' "$PLAN" | uv run astral-versions/scripts/convert-cargo-dist-plan.py | uv run astral-versions/scripts/insert-versions.py --name uv

      - name: "Commit versions"
        working-directory: astral-versions
        run: |
          git config user.name "astral-versions-bot"
          git config user.email "176161322+astral-versions-bot@users.noreply.github.com"

          git checkout -b "$BRANCH_NAME"
          git add -A
          git commit -m "Update uv to version $VERSION"

      - name: "Create Pull Request"
        working-directory: astral-versions
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_VERSIONS_PAT }}
        run: |
          pull_request_title="Add uv $VERSION"

          gh pr list --state open --json title --jq ".[] | select(.title == \"$pull_request_title\") | .number" | \
            xargs -I {} gh pr close {}

          git push origin "$BRANCH_NAME"

          gh pr create --base main --head "$BRANCH_NAME" \
            --title "$pull_request_title" \
            --body "Automated versions update for $VERSION" \
            --label "automation"

      - name: "Merge Pull Request"
        if: ${{ !fromJson(inputs.plan).announcement_tag_is_implicit }}
        working-directory: astral-versions
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_VERSIONS_PAT }}
        run: |
          # Wait for PR to be created before merging
          sleep 10
          gh pr merge --squash "$BRANCH_NAME"
release perms .github/workflows/release.yml
Triggers
workflow_dispatch
Runs on
depot-ubuntu-latest-4, depot-ubuntu-latest-4, depot-ubuntu-latest-4, depot-ubuntu-latest-4, depot-ubuntu-latest-4
Jobs
plan, custom-build-release-binaries, custom-build-docker, synthesize-local-dist-manifest, build-global-artifacts, host, custom-publish-pypi, custom-publish-crates, announce, custom-publish-docs, custom-publish-versions, custom-publish-mirror
Actions
actions/attest-build-provenance
Commands
  • curl --proto '=https' --tlsv1.2 -LsSf "https://github.com/axodotdev/cargo-dist/releases/download/v${CARGO_DIST_VERSION}/cargo-dist-x86_64-unknown-linux-gnu.tar.xz" -o /tmp/cargo-dist.tar.xz echo "${CARGO_DIST_CHECKSUM} /tmp/cargo-dist.tar.xz" | sha256sum -c - tar -xf /tmp/cargo-dist.tar.xz -C /tmp install /tmp/cargo-dist-x86_64-unknown-linux-gnu/dist ~/.cargo/bin/
  • dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json echo "dist ran successfully" cat plan-dist-manifest.json echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
  • chmod +x ~/.cargo/bin/dist
  • temp_manifest=target/local-dist-manifest.json.tmp dist manifest "$TAG" --output-format=json --no-local-paths --artifacts=local > "$temp_manifest" python3 scripts/patch-dist-manifest-checksums.py --manifest "$temp_manifest" --artifacts-dir target/distrib mv "$temp_manifest" target/distrib/local-dist-manifest.json
  • chmod +x ~/.cargo/bin/dist
  • dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json echo "dist ran successfully" # Parse out what we just built and upload it to scratch storage echo "paths<<EOF" >> "$GITHUB_OUTPUT" jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" cp dist-manifest.json "$BUILD_MANIFEST_NAME"
  • chmod +x ~/.cargo/bin/dist
  • dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json echo "artifacts uploaded and released successfully" cat dist-manifest.json echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
View raw YAML
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
#
# Copyright 2022-2024, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a GitHub Release
#
# Note that the GitHub Release will be created with a generated
# title/body based on your changelogs.

name: Release
permissions:
  "contents": "write"

# This task will run whenever you workflow_dispatch with a tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However, GitHub
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
  workflow_dispatch:
    inputs:
      tag:
        description: Release Tag
        required: true
        default: dry-run
        type: string

env:
  CARGO_DIST_VERSION: "0.31.0"
  CARGO_DIST_CHECKSUM: "cd355dab0b4c02fb59038fef87655550021d07f45f1d82f947a34ef98560abb8"

jobs:
  # Run 'dist plan' (or host) to determine what tasks we need to do
  plan:
    runs-on: "depot-ubuntu-latest-4"
    outputs:
      val: ${{ steps.plan.outputs.manifest }}
      tag: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }}
      tag-flag: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }}
      publishing: ${{ inputs.tag && inputs.tag != 'dry-run' }}
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install dist
        shell: bash
        run: |
          curl --proto '=https' --tlsv1.2 -LsSf "https://github.com/axodotdev/cargo-dist/releases/download/v${CARGO_DIST_VERSION}/cargo-dist-x86_64-unknown-linux-gnu.tar.xz" -o /tmp/cargo-dist.tar.xz
          echo "${CARGO_DIST_CHECKSUM}  /tmp/cargo-dist.tar.xz" | sha256sum -c -
          tar -xf /tmp/cargo-dist.tar.xz -C /tmp
          install /tmp/cargo-dist-x86_64-unknown-linux-gnu/dist ~/.cargo/bin/
      - name: Cache dist
        uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/dist
      # sure would be cool if github gave us proper conditionals...
      # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
      # functionality based on whether this is a pull_request, and whether it's from a fork.
      # (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
      # but also really annoying to build CI around when it needs secrets to work right.)
      - id: plan
        run: |
          dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json
          echo "dist ran successfully"
          cat plan-dist-manifest.json
          echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
      - name: "Upload dist-manifest.json"
        uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
        with:
          name: artifacts-plan-dist-manifest
          path: plan-dist-manifest.json

  custom-build-release-binaries:
    needs:
      - plan
    if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
    uses: ./.github/workflows/build-release-binaries.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-build-docker:
    needs:
      - plan
    if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
    uses: ./.github/workflows/build-docker.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    permissions:
      "attestations": "write"
      "contents": "read"
      "id-token": "write"
      "packages": "write"

  synthesize-local-dist-manifest:
    needs:
      - plan
      - custom-build-release-binaries
    if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
    runs-on: "depot-ubuntu-latest-4"
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install cached dist
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/
      - run: chmod +x ~/.cargo/bin/dist
      - name: Fetch local artifacts
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true
      - name: Generate local dist manifest
        shell: bash
        env:
          TAG: ${{ needs.plan.outputs.tag-flag }}
        run: |
          temp_manifest=target/local-dist-manifest.json.tmp
          dist manifest "$TAG" --output-format=json --no-local-paths --artifacts=local > "$temp_manifest"
          python3 scripts/patch-dist-manifest-checksums.py --manifest "$temp_manifest" --artifacts-dir target/distrib
          mv "$temp_manifest" target/distrib/local-dist-manifest.json
      - name: Upload synthesized local dist manifest
        uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
        with:
          name: artifacts-build-local-manifest
          path: target/distrib/local-dist-manifest.json

  # Build and package all the platform-agnostic(ish) things
  build-global-artifacts:
    needs:
      - plan
      - custom-build-release-binaries
      - custom-build-docker
      - synthesize-local-dist-manifest
    runs-on: "depot-ubuntu-latest-4"
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install cached dist
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/
      - run: chmod +x ~/.cargo/bin/dist
      # Get all the local artifacts for the global tasks to use (for e.g. checksums)
      - name: Fetch local artifacts
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true
      - id: cargo-dist
        shell: bash
        run: |
          dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
          echo "dist ran successfully"

          # Parse out what we just built and upload it to scratch storage
          echo "paths<<EOF" >> "$GITHUB_OUTPUT"
          jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
          echo "EOF" >> "$GITHUB_OUTPUT"

          cp dist-manifest.json "$BUILD_MANIFEST_NAME"
      - name: "Upload artifacts"
        uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
        with:
          name: artifacts-build-global
          path: |
            ${{ steps.cargo-dist.outputs.paths }}
            ${{ env.BUILD_MANIFEST_NAME }}
  # Determines if we should publish/announce
  host:
    needs:
      - plan
      - custom-build-release-binaries
      - custom-build-docker
      - build-global-artifacts
    # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
    if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-release-binaries.result == 'skipped' || needs.custom-build-release-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    runs-on: "depot-ubuntu-latest-4"
    outputs:
      val: ${{ steps.host.outputs.manifest }}
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install cached dist
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/
      - run: chmod +x ~/.cargo/bin/dist
      # Fetch artifacts from scratch-storage
      - name: Fetch artifacts
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true
      # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce"
      - id: host
        shell: bash
        run: |
          dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
          echo "artifacts uploaded and released successfully"
          cat dist-manifest.json
          echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
      - name: "Upload dist-manifest.json"
        uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
        with:
          # Overwrite the previous copy
          name: artifacts-dist-manifest
          path: dist-manifest.json

  custom-publish-pypi:
    needs:
      - plan
      - host
    if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
    uses: ./.github/workflows/publish-pypi.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    # publish jobs get escalated permissions
    permissions:
      "id-token": "write"
      "packages": "write"

  custom-publish-crates:
    needs:
      - plan
      - host
      - custom-publish-pypi # DIRTY: see #16989
    if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
    uses: ./.github/workflows/publish-crates.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    # publish jobs get escalated permissions
    permissions:
      "contents": "read"
      "id-token": "write"

  # Create a GitHub Release while uploading all files to it
  announce:
    needs:
      - plan
      - host
      - custom-publish-pypi
      - custom-publish-crates
    # use "always() && ..." to allow us to wait for all publish jobs while
    # still allowing individual publish jobs to skip themselves (for prereleases).
    # "host" however must run to completion, no skipping allowed!
    if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-crates.result == 'skipped' || needs.custom-publish-crates.result == 'success') }}
    runs-on: "depot-ubuntu-latest-4"
    environment:
      name: release
    permissions:
      "attestations": "write"
      "contents": "write"
      "id-token": "write"
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
        with:
          persist-credentials: false
          submodules: recursive
      # Create a GitHub Release while uploading all files to it
      - name: "Download GitHub Artifacts"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          pattern: artifacts-*
          path: artifacts
          merge-multiple: true
      - name: Cleanup
        run: |
          # Remove the granular manifests
          rm -f artifacts/*-dist-manifest.json
      - name: Attest
        uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8
        with:
          subject-path: |
            artifacts/*.json
            artifacts/*.sh
            artifacts/*.ps1
            artifacts/*.zip
            artifacts/*.tar.gz
      - name: Create GitHub Release
        env:
          PRERELEASE_FLAG: "${{ fromJson(needs.host.outputs.val).announcement_is_prerelease && '--prerelease' || '' }}"
          ANNOUNCEMENT_TITLE: "${{ fromJson(needs.host.outputs.val).announcement_title }}"
          ANNOUNCEMENT_BODY: "${{ fromJson(needs.host.outputs.val).announcement_github_body }}"
          RELEASE_COMMIT: "${{ github.sha }}"
        run: |
          # Write and read notes from a file to avoid quoting breaking things
          echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt

          gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*

  custom-publish-docs:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-docs.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-versions:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-versions.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-mirror:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-mirror.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    permissions:
      "contents": "read"
sync-python-releases perms .github/workflows/sync-python-releases.yml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest
Jobs
sync
Actions
astral-sh/setup-uv, Swatinem/rust-cache, peter-evans/create-pull-request
Commands
  • uv run -- fetch-download-metadata.py
  • ${GITHUB_WORKSPACE}/crates/uv-dev/sync_sysconfig_targets.sh
  • uv run --no-project scripts/sync-python-version-constants.py
View raw YAML
# Sync Python releases and create a pull request.
#
# Based on: https://github.com/astral-sh/rye/blob/57b7c089e494138aae29a130afb2e17f447970bf/.github/workflows/sync-python-releases.yml
name: "Sync Python downloads"
on:
  workflow_dispatch:
  schedule:
    - cron: "0 0 * * *"

permissions: {}

jobs:
  sync:
    if: github.repository == 'astral-sh/uv'
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "latest"
          enable-cache: true
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: false
      - name: Sync Python Releases
        run: |
          uv run -- fetch-download-metadata.py
        working-directory: ./crates/uv-python
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Sync Sysconfig Targets
        run: ${GITHUB_WORKSPACE}/crates/uv-dev/sync_sysconfig_targets.sh
        working-directory: ./crates/uv-dev
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: "Update Python version constants"
        run: uv run --no-project scripts/sync-python-version-constants.py

      - name: "Create Pull Request"
        uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
        with:
          commit-message: "Sync latest Python releases"
          add-paths: |
            crates/uv-python/download-metadata.json
            crates/uv-dev/src/generate_sysconfig_mappings.rs
            crates/uv-python/src/sysconfig/generated_mappings.rs
            crates/uv-test/src/lib.rs
          branch: "sync-python-releases"
          title: "Sync latest Python releases"
          body: "Automated update for Python releases."
          base: "main"
          draft: true
test matrix perms .github/workflows/test.yml
Triggers
workflow_call
Runs on
depot-ubuntu-24.04-16, depot-macos-14, github-windows-2025-x86_64-16
Jobs
cargo-test-linux, cargo-test-macos, cargo-test-windows
Matrix
partition→ 1, 2, 3
Actions
Swatinem/rust-cache, astral-sh/setup-uv, taiki-e/install-action, Swatinem/rust-cache, astral-sh/setup-uv, taiki-e/install-action, astral-sh/setup-uv, Swatinem/rust-cache, taiki-e/install-action
Commands
  • ./scripts/install-mold.sh
  • rustup show
  • uv python install
  • sudo apt update -y sudo apt install -y gnome-keyring
  • gnome-keyring-daemon --components=secrets --daemonize --unlock <<< 'foobar'
  • sudo apt-get install -y btrfs-progs truncate -s 1G /tmp/btrfs.img mkfs.btrfs /tmp/btrfs.img sudo mkdir /btrfs sudo mount -o loop /tmp/btrfs.img /btrfs sudo chown "$(id -u):$(id -g)" /btrfs
  • sudo mkdir /tmpfs sudo mount -t tmpfs -o size=256m tmpfs /tmpfs sudo chown "$(id -u):$(id -g)" /tmpfs
  • truncate -s 16M /tmp/minix.img mkfs.minix /tmp/minix.img sudo mkdir /minix sudo mount -o loop /tmp/minix.img /minix sudo chown "$(id -u):$(id -g)" /minix
View raw YAML
on:
  workflow_call:
    inputs:
      save-rust-cache:
        required: false
        type: string
        default: "true"
      test-macos:
        required: false
        type: string
        default: "false"

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  PYTHON_VERSION: "3.12"
  RUSTUP_MAX_RETRIES: 10

jobs:
  # We use the large GitHub actions runners
  # For Ubuntu and Windows, this requires Organization-level configuration
  # See: https://docs.github.com/en/actions/using-github-hosted-runners/about-larger-runners/about-larger-runners#about-ubuntu-and-windows-larger-runners

  cargo-test-linux:
    timeout-minutes: 10
    runs-on: depot-ubuntu-24.04-16
    name: "cargo test on linux"
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install mold"
        run: ./scripts/install-mold.sh

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Install Rust toolchain"
        run: rustup show

      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

      - name: "Install required Python versions"
        run: uv python install

      - name: "Install secret service"
        run: |
          sudo apt update -y
          sudo apt install -y gnome-keyring

      - name: "Start gnome-keyring"
        # run gnome-keyring with 'foobar' as password for the login keyring
        # this will create a new login keyring and unlock it
        # the login password doesn't matter, but the keyring must be unlocked for the tests to work
        run: gnome-keyring-daemon --components=secrets --daemonize --unlock <<< 'foobar'

      - name: "Create btrfs filesystem"
        run: |
          sudo apt-get install -y btrfs-progs
          truncate -s 1G /tmp/btrfs.img
          mkfs.btrfs /tmp/btrfs.img
          sudo mkdir /btrfs
          sudo mount -o loop /tmp/btrfs.img /btrfs
          sudo chown "$(id -u):$(id -g)" /btrfs

      - name: "Create tmpfs filesystem"
        run: |
          sudo mkdir /tmpfs
          sudo mount -t tmpfs -o size=256m tmpfs /tmpfs
          sudo chown "$(id -u):$(id -g)" /tmpfs

      - name: "Create minix filesystem (low hardlink limit)"
        run: |
          truncate -s 16M /tmp/minix.img
          mkfs.minix /tmp/minix.img
          sudo mkdir /minix
          sudo mount -o loop /tmp/minix.img /minix
          sudo chown "$(id -u):$(id -g)" /minix

      - name: "Install cargo nextest"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-nextest

      - name: "Cargo test"
        env:
          # Retry more than default to reduce flakes in CI
          UV_HTTP_RETRIES: 5
          RUST_BACKTRACE: 1
          UV_INTERNAL__TEST_COW_FS: /btrfs
          UV_INTERNAL__TEST_NOCOW_FS: /tmpfs
          UV_INTERNAL__TEST_ALT_FS: /tmpfs
          UV_INTERNAL__TEST_LOWLINKS_FS: /minix
          # Write pending snapshots to a separate directory for artifact upload
          INSTA_UPDATE: new
          INSTA_PENDING_DIR: ${{ github.workspace }}/pending-snapshots
        run: |
          cargo nextest run \
            --cargo-profile fast-build \
            --features test-python-patch,native-auth,secret-service \
            --workspace \
            --profile ci-linux

      - name: "Upload pending snapshots"
        if: ${{ failure() }}
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: pending-snapshots-linux
          path: pending-snapshots/
          if-no-files-found: ignore
          include-hidden-files: true
          retention-days: 14

  cargo-test-macos:
    timeout-minutes: 20
    # Only run macOS tests on main without opt-in
    if: ${{ inputs.test-macos == 'true' }}
    runs-on: depot-macos-14
    name: "cargo test on macos"
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Install Rust toolchain"
        run: rustup show

      - name: "Create HFS+ disk image (no reflink support)"
        run: |
          hdiutil create -size 256m -fs HFS+ -volname NoReflink /tmp/noreflink.dmg
          hdiutil attach /tmp/noreflink.dmg
          echo "HFS_MOUNT=/Volumes/NoReflink" >> "$GITHUB_ENV"

      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

      - name: "Install required Python versions"
        run: uv python install

      - name: "Install cargo nextest"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-nextest

      - name: "Cargo test"
        env:
          # Retry more than default to reduce flakes in CI
          UV_HTTP_RETRIES: 5
          RUST_BACKTRACE: 1
          # macOS tmpdir is on APFS which supports reflink
          UV_INTERNAL__TEST_COW_FS: ${{ runner.temp }}
          # HFS+ RAM disk does not support copy-on-write and is on a different device
          UV_INTERNAL__TEST_NOCOW_FS: ${{ env.HFS_MOUNT }}
          UV_INTERNAL__TEST_ALT_FS: ${{ env.HFS_MOUNT }}
          # Write pending snapshots to a separate directory for artifact upload
          INSTA_UPDATE: new
          INSTA_PENDING_DIR: ${{ github.workspace }}/pending-snapshots
        run: |
          cargo nextest run \
            --cargo-profile fast-build \
            --no-default-features \
            --features test-python,test-python-managed,test-pypi,test-git,test-git-lfs,performance,test-crates-io,native-auth,apple-native \
            --workspace \
            --profile ci-macos

      - name: "Upload pending snapshots"
        if: ${{ failure() }}
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: pending-snapshots-macos
          path: pending-snapshots/
          if-no-files-found: ignore
          include-hidden-files: true
          retention-days: 14

  cargo-test-windows:
    timeout-minutes: 15
    runs-on: github-windows-2025-x86_64-16
    name: "cargo test on windows ${{ matrix.partition }} of 3"
    strategy:
      fail-fast: false
      matrix:
        partition: [1, 2, 3]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup Dev Drive
        run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1

      # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
      - name: Copy Git Repo to Dev Drive
        run: |
          Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse

      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

      - name: "Install required Python versions"
        run: uv python install

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: ${{ env.UV_WORKSPACE }}
          # Use the same cache entry across the partitions
          add-job-id-key: false
          save-if: ${{ inputs.save-rust-cache == 'true' }}

      - name: "Install Rust toolchain"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: rustup show

      - name: "Create NTFS test directory (low hardlink limit)"
        run: New-Item -Path "C:\uv" -ItemType Directory -Force

      - name: "Install cargo nextest"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-nextest

      - name: "Cargo test"
        working-directory: ${{ env.UV_WORKSPACE }}
        env:
          # Retry more than default to reduce flakes in CI
          UV_HTTP_RETRIES: 5
          # Avoid permission errors during concurrent tests
          # See https://github.com/astral-sh/uv/issues/6940
          UV_LINK_MODE: copy
          RUST_BACKTRACE: 1
          UV_INTERNAL__TEST_LOWLINKS_FS: "C:\\uv"
          # Write pending snapshots to a separate directory for artifact upload
          INSTA_UPDATE: new
          INSTA_PENDING_DIR: ${{ github.workspace }}/pending-snapshots
        shell: bash
        run: |
          cargo nextest run \
            --cargo-profile fast-build \
            --no-default-features \
            --features test-python,test-pypi,test-python-managed,native-auth,windows-native \
            --workspace \
            --profile ci-windows \
            --partition hash:${{ matrix.partition }}/3

      - name: "Upload pending snapshots"
        if: ${{ failure() }}
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: pending-snapshots-windows-${{ matrix.partition }}
          path: pending-snapshots/
          if-no-files-found: ignore
          include-hidden-files: true
          retention-days: 14
test-ecosystem matrix perms .github/workflows/test-ecosystem.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
ecosystem-test
Matrix
include, include.commands, include.python, include.ref, include.repo→ 3.12, 3.9, 7f25bbdf45fc81cca6dc23fb6a7377d436b70c83, b78b5a210bde49e7e04b62a2a4f453ca10e0048c, d03bf4a01ca3b378cc8590bd481f307e82115bc6, pallets/flask, prefecthq/prefect, pydantic/pydantic-core, uv lock --upgrade, uv pip install -e '.[dev]', uv pip install -r requirements/dev.txt, uv sync --group all, uv venv
Commands
  • chmod +x ./uv
  • echo '${{ toJSON(matrix.commands) }}' | jq -r '.[]' | while read cmd; do echo "+ $cmd" >&2 if [[ $cmd == uv* ]]; then ./$cmd else $cmd fi done
View raw YAML
on:
  workflow_call:
    inputs:
      sha:
        required: true
        type: string

permissions: {}

jobs:
  ecosystem-test:
    name: "${{ matrix.repo }}"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - repo: "prefecthq/prefect"
            ref: "7f25bbdf45fc81cca6dc23fb6a7377d436b70c83"
            commands:
              - "uv venv"
              - "uv pip install -e '.[dev]'"
            python: "3.9"
          - repo: "pallets/flask"
            ref: "b78b5a210bde49e7e04b62a2a4f453ca10e0048c"
            commands:
              - "uv venv"
              - "uv pip install -r requirements/dev.txt"
            python: "3.12"
          - repo: "pydantic/pydantic-core"
            ref: "d03bf4a01ca3b378cc8590bd481f307e82115bc6"
            commands:
              - "uv sync --group all"
              - "uv lock --upgrade"
            python: "3.12"
      fail-fast: false
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          repository: ${{ matrix.repo }}
          ref: ${{ matrix.ref }}
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: ${{ matrix.python }}

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Test"
        run: |
          echo '${{ toJSON(matrix.commands) }}' | jq -r '.[]' | while read cmd; do
            echo "+ $cmd" >&2
            if [[ $cmd == uv* ]]; then
              ./$cmd
            else
              $cmd
            fi
          done
test-integration perms .github/workflows/test-integration.yml
Triggers
workflow_call
Runs on
ubuntu-latest, ubuntu-latest, macos-15-intel, ubuntu-latest, github-ubuntu-24.04-aarch64-4, windows-latest, windows-11-arm, windows-11-arm, windows-latest, windows-latest, ubuntu-latest, windows-latest, ubuntu-latest, windows-latest, ubuntu-latest, windows-latest, ubuntu-24.04, ubuntu-latest, ubuntu-latest, windows-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, macos-14
Jobs
integration-test-nushell, integration-test-conda, integration-test-conda-macos-x86_64, integration-test-deadsnakes-39-linux, integration-test-linux-armv7-on-aarch64, integration-test-free-threaded-windows-x86_64, integration-test-windows-aarch64-implicit, integration-test-windows-aarch64-explicit, integration-test-windows-python-install-manager, integration-test-windows-registry, integration-test-pypy-linux, integration-test-pypy-windows-x86_64, integration-test-graalpy-linux, integration-test-graalpy-windows-x86_64, integration-test-pyodide-linux, integration-test-pyodide-windows, integration-test-termux, integration-test-github-actions, integration-test-github-actions-freethreaded, integration-test-wsl, integration-test-registries, integration-uv-build-backend, cache-test-ubuntu, cache-test-macos-aarch64
Actions
conda-incubator/setup-miniconda, conda-incubator/setup-miniconda, Vampire/setup-wsl, aws-actions/configure-aws-credentials, google-github-actions/auth, google-github-actions/setup-gcloud
Commands
  • # get latest nushell tag name nu_latest=$(gh release list --repo nushell/nushell --limit 1 --exclude-pre-releases --exclude-drafts --json "tagName" --jq '.[0].tagName') # trim any trailing whitespace from output nu_tag=${nu_latest%%[[:space:]]*} # download binary for x86_64-unknown-linux-gnu target gh release download ${nu_tag} --repo nushell/nushell --pattern "nu-${nu_tag}-x86_64-unknown-linux-gnu.tar.gz" # extract nu binary from tar.gz tar -xf "nu-${nu_tag}-x86_64-unknown-linux-gnu.tar.gz" # make the binary executable chmod +x "./nu-${nu_tag}-x86_64-unknown-linux-gnu/nu" # add it to PATH echo "${{ github.workspace }}/nu-${nu_tag}-x86_64-unknown-linux-gnu" >> "${GITHUB_PATH}"
  • chmod +x ./uv
  • ./uv venv
  • overlay use ${{ github.workspace }}/.venv/bin/activate.nu
  • chmod +x ./uv
  • conda info
  • echo "$CONDA_PREFIX" ./uv pip install anyio
  • chmod +x ./uv
View raw YAML
on:
  workflow_call:
    inputs:
      sha:
        description: "The commit SHA to use for artifact names"
        required: true
        type: string

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  PYTHON_VERSION: "3.12"
  RUSTUP_MAX_RETRIES: 10

jobs:
  integration-test-nushell:
    name: "nushell"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Install nushell
        env:
          # This token only needs read access to the GitHub repository nushell/nushell.
          # This token is used (via gh-cli) to avoid hitting GitHub REST API rate limits.
          GITHUB_TOKEN: ${{ github.token }}
        run: |-
          # get latest nushell tag name
          nu_latest=$(gh release list --repo nushell/nushell --limit 1 --exclude-pre-releases --exclude-drafts --json "tagName" --jq '.[0].tagName')
          # trim any trailing whitespace from output
          nu_tag=${nu_latest%%[[:space:]]*}

          # download binary for x86_64-unknown-linux-gnu target
          gh release download ${nu_tag} --repo nushell/nushell --pattern "nu-${nu_tag}-x86_64-unknown-linux-gnu.tar.gz"

          # extract nu binary from tar.gz
          tar -xf "nu-${nu_tag}-x86_64-unknown-linux-gnu.tar.gz"
          # make the binary executable
          chmod +x "./nu-${nu_tag}-x86_64-unknown-linux-gnu/nu"
          # add it to PATH
          echo "${{ github.workspace }}/nu-${nu_tag}-x86_64-unknown-linux-gnu" >> "${GITHUB_PATH}"

      - name: Download binary
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: Prepare binary
        run: chmod +x ./uv

      - name: Create venv
        # The python version is arbitrary for this test.
        # We only want to ensure the activation script behaves properly
        run: ./uv venv

      - name: Activate venv
        shell: nu {0}
        run: overlay use ${{ github.workspace }}/.venv/bin/activate.nu

  integration-test-conda:
    name: "conda on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167 # v3.3.0
        with:
          miniconda-version: latest
          activate-environment: uv
          python-version: "3.12"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: Conda info
        shell: bash -el {0}
        run: conda info

      - name: "Install a package"
        shell: bash -el {0}
        run: |
          echo "$CONDA_PREFIX"
          ./uv pip install anyio

  integration-test-conda-macos-x86_64:
    name: "conda on macos x86-64"
    timeout-minutes: 10
    runs-on: macos-15-intel # github-macos-15-x86_64-4
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167 # v3.3.0
        with:
          miniconda-version: latest
          activate-environment: uv
          python-version: "3.12"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-macos-x86_64-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: Conda info
        shell: bash -el {0}
        run: conda info

      - name: "Install a package with macOS macOS 11.0 wheels"
        shell: bash -el {0}
        run: |
          # greenlet has `macosx_11_0_universal2` wheels. Previously, conda interpreters on Intel
          # macOS reported the incorrect platform tag `macosx_10_16` causing uv to consider the
          # wheel incompatible. See https://github.com/astral-sh/uv/issues/14267
          #
          # Using `--only-binary` ensures this fails if the platform tags are wrong instead of
          # falling back to a source build.
          ./uv pip install --no-build 'greenlet==3.3.2'

  integration-test-deadsnakes-39-linux:
    name: "deadsnakes python3.9 on ubuntu"
    timeout-minutes: 15
    runs-on: ubuntu-latest
    steps:
      - name: "Install python3.9"
        run: |
          for i in {1..5}; do
            sudo add-apt-repository ppa:deadsnakes && break || { echo "Attempt $i failed, retrying in 10 seconds..."; sleep 10; }
            if [ $i -eq 5 ]; then
              echo "Failed to add repository after 5 attempts"
              exit 1
            fi
          done
          sudo apt-get update
          sudo apt-get install python3.9

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Check missing distutils"
        run: |
          ./uv venv -p 3.9 --python-preference only-system -v 2>&1 | tee log.txt || true
          # We should report that distutils is missing
          grep 'Python installation is missing `distutils`' log.txt

      - name: "Install distutils"
        run: |
          sudo apt-get install python3.9-distutils

      - name: "Create a virtualenv"
        run: |
          ./uv venv -p 3.9 --python-preference only-system -v

      - name: "Check version"
        run: |
          .venv/bin/python --version

      - name: "Check install"
        run: |
          ./uv pip install -v anyio

  integration-test-linux-armv7-on-aarch64:
    name: "armv7 on aarch64 linux"
    timeout-minutes: 20
    runs-on: github-ubuntu-24.04-aarch64-4
    env:
      UV_PYTHON_INSTALL_DIR: ${{ github.workspace }}/.python

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-armv7-gnueabihf-${{ inputs.sha }}

      - name: "Install armhf runtime"
        run: |
          sudo dpkg --add-architecture armhf
          sudo apt-get update
          sudo apt-get install -y libc6:armhf libgcc-s1:armhf

      - name: "Prepare binary"
        run: |
          chmod +x ./uv
          chmod +x ./uvx

      - name: "Install Python via uv"
        run: |
          ./uv python install -v 3.13.12

      - name: "Verify hard-float variant selected"
        run: |
          python_path=$(./uv python find 3.13)
          expected="$GITHUB_WORKSPACE/.python/cpython-3.13-linux-armv7-gnueabihf/bin/python3.13"
          if [[ "$python_path" != "$expected" ]]; then
            echo "Expected: $expected"
            echo "Found:    $python_path"
            exit 1
          fi

      - name: "Create a virtual environment"
        run: |
          ./uv venv -c -p 3.13.12 --managed-python --no-python-downloads

  integration-test-free-threaded-windows-x86_64:
    name: "free-threaded on windows"
    timeout-minutes: 10
    runs-on: windows-latest

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Install free-threaded Python via uv"
        run: |
          ./uv python install -v 3.13t

      - name: "Create a virtual environment (stdlib)"
        run: |
          & (./uv python find 3.13t) -m venv .venv

      - name: "Check version (stdlib)"
        run: |
          .venv/Scripts/python --version

      - name: "Create a virtual environment (uv)"
        run: |
          ./uv venv -c -p 3.13t --managed-python

      - name: "Check version (uv)"
        run: |
          .venv/Scripts/python --version

      - name: "Check is free-threaded"
        run: |
          .venv/Scripts/python -c "import sys; exit(1) if sys._is_gil_enabled() else exit(0)"

      - name: "Check install"
        run: |
          ./uv pip install -v anyio

      - name: "Check uv run"
        run: |
          ./uv run python -c ""
          ./uv run -p 3.13t python -c ""

  integration-test-windows-aarch64-implicit:
    name: "aarch64 windows implicit"
    timeout-minutes: 10
    runs-on: windows-11-arm

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-aarch64-${{ inputs.sha }}

      - name: "Install Python via uv (implicitly select x64)"
        run: |
          ./uv python install -v 3.13

      - name: "Create a virtual environment (stdlib)"
        run: |
          & (./uv python find 3.13) -m venv .venv

      - name: "Check version (stdlib)"
        run: |
          .venv/Scripts/python --version

      - name: "Create a virtual environment (uv)"
        run: |
          ./uv venv -c -p 3.13 --managed-python

      - name: "Check version (uv)"
        run: |
          .venv/Scripts/python --version

      - name: "Check is x64"
        run: |
          .venv/Scripts/python -c "import sys; exit(1) if 'AMD64' not in sys.version else exit(0)"

      - name: "Check install"
        run: |
          ./uv pip install -v anyio

      - name: "Check uv run"
        run: |
          ./uv run python -c ""
          ./uv run -p 3.13 python -c ""

  integration-test-windows-aarch64-explicit:
    name: "aarch64 windows explicit"
    timeout-minutes: 10
    runs-on: windows-11-arm

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-aarch64-${{ inputs.sha }}

      - name: "Install Python via uv (explicitly select aarch64)"
        run: |
          ./uv python install -v cpython-3.13-windows-aarch64-none

      - name: "Create a virtual environment (stdlib)"
        run: |
          & (./uv python find 3.13) -m venv .venv

      - name: "Check version (stdlib)"
        run: |
          .venv/Scripts/python --version

      - name: "Create a virtual environment (uv)"
        run: |
          ./uv venv -c -p 3.13 --managed-python

      - name: "Check version (uv)"
        run: |
          .venv/Scripts/python --version

      - name: "Check is NOT x64"
        run: |
          .venv/Scripts/python -c "import sys; exit(1) if 'AMD64' in sys.version else exit(0)"

      - name: "Check install"
        run: |
          ./uv pip install -v anyio

      - name: "Check uv run"
        run: |
          ./uv run python -c ""
          ./uv run -p 3.13 python -c ""

  integration-test-windows-python-install-manager:
    name: "windows python install manager"
    timeout-minutes: 10
    runs-on: windows-latest

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Install Python via Python Install manager"
        run: |
          # https://www.python.org/downloads/release/pymanager-250/
          winget install --accept-package-agreements --accept-source-agreements 9NQ7512CXL7T
          # Call Python Install Manager's py.exe by full path to avoid legacy py.exe
          & "$env:LOCALAPPDATA\Microsoft\WindowsApps\py.exe" install 3.14

      # https://github.com/astral-sh/uv/issues/16204
      - name: "Check temporary environment creation"
        run: |
          ./uv run -p $env:LOCALAPPDATA\Python\pythoncore-3.14-64\python.exe --with numpy python -c "import sys; print(sys.executable)"

  # Test PEP 514 integration that installs Python into the Windows registry.
  integration-test-windows-registry:
    name: "windows registry"
    timeout-minutes: 10
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Test PEP 514 registration"
        run: python ./scripts/check_registry.py --uv ./uv.exe

  integration-test-pypy-linux:
    name: "pypy on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Install PyPy"
        run: ./uv python install -v pypy3.9

      - name: "Create a virtual environment"
        run: |
          ./uv venv -p pypy3.9 --managed-python

      - name: "Check for executables"
        run: |
          check_in_bin() {
              local executable_name=$1
              local bin_path=".venv/bin"

              if [[ -x "$bin_path/$executable_name" ]]; then
                  return 0
              else
                  echo "Executable '$executable_name' not found in folder '$bin_path'."
                  return 1
              fi
          }

          executables=("pypy" "pypy3" "python")

          all_found=true
          for executable_name in "${executables[@]}"; do
              check_in_bin "$executable_name" "$folder_path"
              result=$?

              if [[ $result -ne 0 ]]; then
                  all_found=false
              fi
          done

          if ! $all_found; then
            echo "One or more expected executables were not found."
            exit 1
          fi

      - name: "Check version"
        run: |
          .venv/bin/pypy --version
          .venv/bin/pypy3 --version
          .venv/bin/python --version

      - name: "Check install"
        run: |
          ./uv pip install anyio

  integration-test-pypy-windows-x86_64:
    name: "pypy on windows"
    timeout-minutes: 10
    runs-on: windows-latest

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Install PyPy"
        run: .\uv.exe python install pypy3.9

      - name: "Create a virtual environment"
        run: |
          .\uv.exe venv -p pypy3.9 --managed-python

      - name: "Check for executables"
        shell: python
        run: |
          import sys
          from pathlib import Path

          def binary_exist(binary):
            binaries_path = Path(".venv\\Scripts")
            if (binaries_path / binary).exists():
              return True
            print(f"Executable '{binary}' not found in folder '{binaries_path}'.")

          all_found = True
          expected_binaries = [
              "pypy3.9.exe",
              "pypy3.9w.exe",
              "pypy3.exe",
              "pypyw.exe",
              "python.exe",
              "python3.9.exe",
              "python3.exe",
              "pythonw.exe",
          ]
          for binary in expected_binaries:
            if not binary_exist(binary):
              all_found = False

          if not all_found:
            print("One or more expected executables were not found.")
            sys.exit(1)

      - name: "Check version"
        run: |
          & .venv\Scripts\pypy3.9.exe --version
          & .venv\Scripts\pypy3.exe --version
          & .venv\Scripts\python.exe --version

      - name: "Check install"
        run: |
          .\uv.exe pip install anyio

  integration-test-graalpy-linux:
    name: "graalpy on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Install GraalPy"
        run: ./uv python install -v graalpy

      - name: "Create a virtual environment"
        run: |
          ./uv venv -p graalpy --managed-python

      - name: "Check for executables"
        run: |
          check_in_bin() {
              local executable_name=$1
              local bin_path=".venv/bin"

              if [[ -x "$bin_path/$executable_name" ]]; then
                  return 0
              else
                  echo "Executable '$executable_name' not found in folder '$bin_path'."
                  return 1
              fi
          }

          executables=("graalpy" "python3" "python")

          all_found=true
          for executable_name in "${executables[@]}"; do
              check_in_bin "$executable_name" "$folder_path"
              result=$?

              if [[ $result -ne 0 ]]; then
                  all_found=false
              fi
          done

          if ! $all_found; then
            echo "One or more expected executables were not found."
            exit 1
          fi

      - name: "Check version"
        run: |
          .venv/bin/graalpy --version
          .venv/bin/python3 --version
          .venv/bin/python --version

      - name: "Check install"
        run: |
          ./uv pip install anyio

      - name: "Check a GraalPy dev version (different version parsing)"
        run: |
          curl -sLf https://github.com/graalvm/graal-languages-ea-builds/releases/download/graalpy-25.0.0-ea.31/graalpy-25.0.0-ea.31-linux-amd64.tar.gz | tar xz
          ./uv run --no-project -p ./graalpy-25.0.0-dev-linux-amd64/bin/python python --version

  integration-test-graalpy-windows-x86_64:
    name: "graalpy on windows"
    timeout-minutes: 10
    runs-on: windows-latest

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Install GraalPy"
        run: .\uv.exe python install graalpy

      - name: "Create a virtual environment"
        run: |
          .\uv.exe venv -p graalpy --managed-python

      - name: "Check for executables"
        shell: python
        run: |
          import sys
          from pathlib import Path

          def binary_exist(binary):
            binaries_path = Path(".venv\\Scripts")
            if (binaries_path / binary).exists():
              return True
            print(f"Executable '{binary}' not found in folder '{binaries_path}'.")

          all_found = True
          expected_binaries = [
              "graalpy.exe",
              "python.exe",
              "python3.exe",
          ]
          for binary in expected_binaries:
            if not binary_exist(binary):
              all_found = False

          if not all_found:
            print("One or more expected executables were not found.")
            sys.exit(1)

      - name: "Check version"
        run: |
          & .venv\Scripts\graalpy.exe --version
          & .venv\Scripts\python3.exe --version
          & .venv\Scripts\python.exe --version

      - name: "Check install"
        run: |
          .\uv.exe pip install anyio

  integration-test-pyodide-linux:
    name: "pyodide on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Create a native virtual environment"
        run: |
          ./uv venv venv-native -p 3.12
          # We use features added in 0.30.3 but there is no known breakage in
          # newer versions.
          ./uv pip install -p venv-native/bin/python pyodide-build==0.30.7 pip

      - name: "Install Pyodide interpreter"
        run: |
          source ./venv-native/bin/activate
          pyodide xbuildenv install 0.27.5
          PYODIDE_PYTHON=$(pyodide config get interpreter)
          PYODIDE_INDEX=$(pyodide config get package_index)
          echo "PYODIDE_PYTHON=$PYODIDE_PYTHON" >> $GITHUB_ENV
          echo "PYODIDE_INDEX=$PYODIDE_INDEX" >> $GITHUB_ENV

      - name: "Create Pyodide virtual environment"
        run: |
          ./uv venv -p $PYODIDE_PYTHON venv-pyodide
          source ./venv-pyodide/bin/activate
          ./uv pip install --extra-index-url=$PYODIDE_INDEX --no-build numpy
          python -c 'import numpy'

      - name: "Install Pyodide with uv python"
        run: |
          ./uv python install cpython-3.13.2-emscripten-wasm32-musl

      - name: "Create a Pyodide virtual environment using uv installed Python"
        run: |
          ./uv venv -p cpython-3.13.2-emscripten-wasm32-musl venv-pyodide2
          # TODO: be able to install Emscripten wheels here...
          source ./venv-pyodide2/bin/activate
          ./uv pip install packaging
          python -c 'import packaging'

  integration-test-pyodide-windows:
    name: "pyodide on windows"
    timeout-minutes: 10
    runs-on: windows-latest
    # Github Actions Windows runner uses D drive as pwd.
    # Pyodide CLI has an issue running cross-drive,
    # So ensure that cache and Pyodide paths are on D drive as well.
    env:
      UV_CACHE_DIR: D:\uv-cache
      PYODIDE_XBUILDENV_PATH: D:\pyodide-xbuildenv
    steps:
      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Create a native virtual environment"
        run: |
          .\uv.exe venv venv-native -p 3.13
          .\uv.exe pip install -p venv-native\Scripts\python.exe pyodide-build==0.31.1 pip

      - name: "Install Pyodide interpreter"
        run: |
          & .\venv-native\Scripts\Activate.ps1
          pyodide xbuildenv install 0.29.2
          $env:PYODIDE_PYTHON = pyodide config get interpreter
          $env:PYODIDE_INDEX = pyodide config get package_index
          echo "PYODIDE_PYTHON=$env:PYODIDE_PYTHON" >> $env:GITHUB_ENV
          echo "PYODIDE_INDEX=$env:PYODIDE_INDEX" >> $env:GITHUB_ENV

      - name: "Create Pyodide virtual environment"
        run: |
          .\uv.exe venv -p $env:PYODIDE_PYTHON venv-pyodide
          & .\venv-pyodide\Scripts\Activate.ps1
          .\uv.exe pip install --extra-index-url=$env:PYODIDE_INDEX --no-build numpy
          python -c 'import numpy'

  integration-test-termux:
    name: "termux on android"
    timeout-minutes: 15
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Run uv on Termux"
        run: |
          docker run --rm \
            -v "$PWD/uv:/uv" \
            -v "$PWD/test/integration/termux.sh:/test-termux.sh" \
            ghcr.io/astral-sh/termux-python:latest \
            /entrypoint.sh bash /test-termux.sh

  integration-test-github-actions:
    name: "github actions"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.12.7"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Install a package without system opt-in"
        run: |
          ./uv pip install anyio && exit 1 || echo "Failed as expected"

      - name: "Install a package with system opt-in"
        run: |
          ./uv pip install anyio --system

      - name: Configure uv to use the system Python by default
        run: echo "UV_SYSTEM_PYTHON=1" >> $GITHUB_ENV

      - name: "Install a package with system opt-in via the environment"
        run: |
          ./uv pip install anyio --reinstall

      - name: "Create a project"
        run: |
          # Use Python 3.11 as the minimum required version
          ./uv init --python 3.11
          ./uv add anyio

      - name: "Sync to the system Python"
        run: ./uv sync -v --python 3.12
        env:
          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 -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.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 -v && { echo "ci: Error; should not succeed"; exit 1; } || { echo "ci: Ok; expected failure"; exit 0; }
        env:
          UV_PROJECT_ENVIRONMENT: "/home/runner/example"

  integration-test-github-actions-freethreaded:
    name: "free-threaded on github actions"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.13t"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Install a package without system opt-in"
        run: |
          ./uv pip install anyio && exit 1 || echo "Failed as expected"

      - name: "Install a package with system opt-in but without free-threaded opt-in"
        run: |
          ./uv pip install anyio --system --python 3.13 || echo "Failed as expected"
          # (we need to request 3.13 or we'll discover 3.12 on the system)

      - name: "Install a package with system and free-threaded opt-in"
        run: |
          ./uv pip install anyio --system --python 3.13t

      - name: "Create a virtual environment"
        run: |
          ./uv venv -p 3.13t --python-preference only-system

      - name: "Check is free-threaded"
        run: |
          .venv/bin/python -c "import sys; exit(1) if sys._is_gil_enabled() else exit(0)"

  integration-test-wsl:
    name: "pyenv on wsl"
    timeout-minutes: 15
    runs-on: windows-latest
    if: ${{ github.event.pull_request.head.repo.fork != true }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Setup WSL"
        uses: Vampire/setup-wsl@6a8db447be7ed35f2f499c02c6e60ff77ef11278 # v6.0.0
        with:
          distribution: Ubuntu-22.04

      - name: "Install pyenv-win"
        shell: pwsh
        run: |
          Write-Host "Installing pyenv-win..."
          Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"
          .\install-pyenv-win.ps1

          # Add pyenv-win to PATH for this session
          $env:PYENV = "$env:USERPROFILE\.pyenv\pyenv-win"
          $env:PATH = "$env:PYENV\bin;$env:PYENV\shims;$env:PATH"

          # Add to GITHUB_PATH so WSL can find the shims
          echo "$env:PYENV\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
          echo "$env:PYENV\shims" | Out-File -FilePath $env:GITHUB_PATH -Append

          Write-Host "Installing Python 3.11.9 via pyenv-win..."
          & "$env:PYENV\bin\pyenv.bat" install 3.11.9
          & "$env:PYENV\bin\pyenv.bat" global 3.11.9

          Write-Host "Verifying pyenv-win installation..."
          & "$env:PYENV\bin\pyenv.bat" versions

      - name: "Test uv"
        shell: wsl-bash {0}
        run: |
          set -x
          chmod +x ./uv

          # Check that we don't fail on `pyenv-win` shims
          ./uv python list -v

  integration-test-registries:
    name: "registries"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    if: ${{ github.event.pull_request.head.repo.fork != true }}
    environment:
      name: uv-test-registries
      deployment: false
    env:
      PYTHON_VERSION: 3.12
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "${{ env.PYTHON_VERSION }}"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Configure AWS credentials"
        uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: "Get AWS CodeArtifact token"
        run: |
          UV_TEST_AWS_TOKEN=$(aws codeartifact get-authorization-token \
            --domain tests \
            --domain-owner ${{ secrets.AWS_ACCOUNT_ID }} \
            --region us-east-1 \
            --query authorizationToken \
            --output text)
          echo "::add-mask::$UV_TEST_AWS_TOKEN"
          echo "UV_TEST_AWS_TOKEN=$UV_TEST_AWS_TOKEN" >> $GITHUB_ENV

      - name: "Authenticate with GCP"
        id: "auth"
        uses: "google-github-actions/auth@fc2174804b84f912b1f6d334e9463f484f1c552d"
        with:
          credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}"

      - name: "Set up GCP SDK"
        uses: "google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db"

      - name: "Get GCP Artifact Registry token"
        id: get_token
        run: |
          UV_TEST_GCP_TOKEN=$(gcloud auth print-access-token)
          echo "::add-mask::$UV_TEST_GCP_TOKEN"
          echo "UV_TEST_GCP_TOKEN=$UV_TEST_GCP_TOKEN" >> $GITHUB_ENV

      - name: "Run registry tests with environment variable backend"
        run: ./uv run --no-project -p "${PYTHON_VERSION}" scripts/registries-test.py --uv ./uv --color always --all --auth-method env
        env:
          RUST_LOG: uv=debug
          UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }}
          UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }}
          UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }}
          UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }}
          UV_TEST_AWS_USERNAME: aws
          UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }}
          UV_TEST_AZURE_URL: ${{ secrets.UV_TEST_AZURE_URL }}
          UV_TEST_AZURE_USERNAME: dummy
          UV_TEST_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_CLOUDSMITH_TOKEN }}
          UV_TEST_CLOUDSMITH_URL: ${{ secrets.UV_TEST_CLOUDSMITH_URL }}
          UV_TEST_CLOUDSMITH_USERNAME: ${{ secrets.UV_TEST_CLOUDSMITH_USERNAME }}
          UV_TEST_GCP_URL: ${{ secrets.UV_TEST_GCP_URL }}
          UV_TEST_GCP_USERNAME: oauth2accesstoken
          UV_TEST_GEMFURY_TOKEN: ${{ secrets.UV_TEST_GEMFURY_TOKEN }}
          UV_TEST_GEMFURY_URL: ${{ secrets.UV_TEST_GEMFURY_URL }}
          UV_TEST_GEMFURY_USERNAME: ${{ secrets.UV_TEST_GEMFURY_USERNAME }}
          UV_TEST_GITLAB_TOKEN: ${{ secrets.UV_TEST_GITLAB_TOKEN }}
          UV_TEST_GITLAB_URL: ${{ secrets.UV_TEST_GITLAB_URL }}
          UV_TEST_GITLAB_USERNAME: token

      - name: "Run registry tests with text store backend"
        run: ./uv run --no-project -p "${PYTHON_VERSION}" scripts/registries-test.py --uv ./uv --color always --all --auth-method text-store
        env:
          RUST_LOG: uv=debug
          UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }}
          UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }}
          UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }}
          UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }}
          UV_TEST_AWS_USERNAME: aws
          UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }}
          UV_TEST_AZURE_URL: ${{ secrets.UV_TEST_AZURE_URL }}
          UV_TEST_AZURE_USERNAME: dummy
          UV_TEST_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_CLOUDSMITH_TOKEN }}
          UV_TEST_CLOUDSMITH_URL: ${{ secrets.UV_TEST_CLOUDSMITH_URL }}
          UV_TEST_CLOUDSMITH_USERNAME: ${{ secrets.UV_TEST_CLOUDSMITH_USERNAME }}
          UV_TEST_GCP_URL: ${{ secrets.UV_TEST_GCP_URL }}
          UV_TEST_GCP_USERNAME: oauth2accesstoken
          UV_TEST_GEMFURY_TOKEN: ${{ secrets.UV_TEST_GEMFURY_TOKEN }}
          UV_TEST_GEMFURY_URL: ${{ secrets.UV_TEST_GEMFURY_URL }}
          UV_TEST_GEMFURY_USERNAME: ${{ secrets.UV_TEST_GEMFURY_USERNAME }}
          UV_TEST_GITLAB_TOKEN: ${{ secrets.UV_TEST_GITLAB_TOKEN }}
          UV_TEST_GITLAB_URL: ${{ secrets.UV_TEST_GITLAB_URL }}
          UV_TEST_GITLAB_USERNAME: token

  integration-uv-build-backend:
    name: "uv_build"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "${{ env.PYTHON_VERSION }}"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: |
          chmod +x ./uv
          chmod +x ./uvx

      - name: "Test uv_build package"
        run: |
          # Build the Python package, which is not covered by uv's integration tests since they can't depend on having
          # a Python package (only the binary itself is built before running Rust's tests)
          ./uv build -v crates/uv-build

          # Test the main path (`build_wheel`) through pip
          ./uv venv -v --seed
          ./uv run --no-project python -m pip install -v test/packages/built-by-uv --find-links crates/uv-build/dist --no-index --no-deps
          ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"

          # Test both `build_wheel` and `build_sdist` through uv
          ./uv venv -c -v
          ./uv build -v --force-pep517 test/packages/built-by-uv --find-links crates/uv-build/dist --offline
          ./uv pip install -v test/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
          ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"

          # Test both `build_wheel` and `build_sdist` through the official `build`
          rm -rf test/packages/built-by-uv/dist/
          ./uv venv -c -v
          ./uv pip install build
          # Add the uv binary to PATH for `build` to find
          PATH="$(pwd):$PATH" UV_OFFLINE=1 UV_FIND_LINKS=crates/uv-build/dist ./uv run --no-project python -m build -v --installer uv test/packages/built-by-uv
          ./uv pip install -v test/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
          ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"

  cache-test-ubuntu:
    name: "cache on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.12"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Download binary for last version"
        run: curl -LsSf "https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-unknown-linux-gnu.tar.gz" | tar -xvz

      - name: "Check cache compatibility"
        run: python scripts/check_cache_compat.py --uv-current ./uv --uv-previous ./uv-x86_64-unknown-linux-gnu/uv

  cache-test-macos-aarch64:
    name: "cache on macos aarch64"
    timeout-minutes: 10
    runs-on: macos-14
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-macos-aarch64-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Download binary for last version"
        run: curl -LsSf "https://github.com/astral-sh/uv/releases/latest/download/uv-aarch64-apple-darwin.tar.gz" | tar -xvz

      - name: "Check cache compatibility"
        run: python scripts/check_cache_compat.py --uv-current ./uv --uv-previous ./uv-aarch64-apple-darwin/uv
test-smoke perms .github/workflows/test-smoke.yml
Triggers
workflow_call
Runs on
ubuntu-latest, github-ubuntu-24.04-aarch64-2, ubuntu-latest, macos-latest, windows-latest, windows-11-arm
Jobs
smoke-test-linux, smoke-test-linux-aarch64, smoke-test-linux-musl, smoke-test-macos, smoke-test-windows-x86_64, smoke-test-windows-aarch64
Commands
  • chmod +x ./uv chmod +x ./uvx
  • ./uv run --no-project scripts/smoke-test
  • eval "$(./uv generate-shell-completion bash)" eval "$(./uvx --generate-shell-completion bash)"
  • chmod +x ./uv chmod +x ./uvx
  • ./uv run --no-project scripts/smoke-test
  • eval "$(./uv generate-shell-completion bash)" eval "$(./uvx --generate-shell-completion bash)"
  • chmod +x ./uv chmod +x ./uvx
  • ./uv run --no-project scripts/smoke-test
View raw YAML
on:
  workflow_call:
    inputs:
      sha:
        description: "The commit SHA to use for artifact names"
        required: true
        type: string

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  smoke-test-linux:
    name: "linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: |
          chmod +x ./uv
          chmod +x ./uvx

      - name: "Smoke test"
        run: |
          ./uv run --no-project scripts/smoke-test

      - name: "Test shell completions"
        run: |
          eval "$(./uv generate-shell-completion bash)"
          eval "$(./uvx --generate-shell-completion bash)"

  smoke-test-linux-aarch64:
    name: "linux aarch64"
    timeout-minutes: 10
    runs-on: github-ubuntu-24.04-aarch64-2
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-aarch64-${{ inputs.sha }}

      - name: "Prepare binary"
        run: |
          chmod +x ./uv
          chmod +x ./uvx

      - name: "Smoke test"
        run: |
          ./uv run --no-project scripts/smoke-test

      - name: "Test shell completions"
        run: |
          eval "$(./uv generate-shell-completion bash)"
          eval "$(./uvx --generate-shell-completion bash)"

  smoke-test-linux-musl:
    name: "linux musl"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    container: alpine:latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: |
          chmod +x ./uv
          chmod +x ./uvx

      - name: "Smoke test"
        run: |
          ./uv run --no-project scripts/smoke-test

  smoke-test-macos:
    name: "macos"
    timeout-minutes: 10
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-macos-x86_64-${{ inputs.sha }}

      - name: "Prepare binary"
        run: |
          chmod +x ./uv
          chmod +x ./uvx

      - name: "Smoke test"
        run: |
          ./uv run --no-project scripts/smoke-test

      - name: "Test shell completions"
        run: |
          eval "$(./uv generate-shell-completion bash)"
          eval "$(./uvx --generate-shell-completion bash)"

  smoke-test-windows-x86_64:
    name: "windows x86_64"
    timeout-minutes: 10
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Smoke test"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: |
          ./uv run --no-project scripts/smoke-test

      - name: "Test uv shell completions"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: |
          (& ./uv generate-shell-completion powershell) | Out-String | Invoke-Expression

      - name: "Test uvx shell completions"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: |
          (& ./uvx --generate-shell-completion powershell) | Out-String | Invoke-Expression

  smoke-test-windows-aarch64:
    name: "windows aarch64"
    timeout-minutes: 10
    runs-on: windows-11-arm
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-aarch64-${{ inputs.sha }}

      - name: "Smoke test"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: |
          ./uv run --no-project scripts/smoke-test

      - name: "Test uv shell completions"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: |
          (& ./uv generate-shell-completion powershell) | Out-String | Invoke-Expression

      - name: "Test uvx shell completions"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: |
          (& ./uvx --generate-shell-completion powershell) | Out-String | Invoke-Expression
test-system matrix perms .github/workflows/test-system.yml
Triggers
workflow_call
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, macos-14, macos-14, macos-14, macos-15-intel, windows-latest, windows-latest, windows-latest, windows-11-arm, windows-11-arm, windows-latest, ubuntu-latest, ubuntu-latest, ${{ matrix.runner }}, ubuntu-latest, windows-latest
Jobs
system-test-debian, system-test-fedora, system-test-ubuntu, system-test-python-36, system-test-python-37, system-test-rocky-linux, system-test-graalpy, system-test-pypy, system-test-pyston, system-test-chainguard-dev, system-test-chainguard, system-test-alpine, system-test-macos-aarch64, system-test-macos-aarch64-homebrew, system-test-macos-aarch64-emulated, system-test-macos-x86_64, system-test-windows-python-310, system-test-windows-x86_64-python-310, system-test-windows-x86_64-python-313, system-test-windows-aarch64-x86-python-313, system-test-windows-aarch64-aarch64-python-313, system-test-choco, system-test-pyenv, system-test-linux-313, system-test-conda, system-test-amazonlinux, system-test-windows-embedded-python-310
Matrix
include, include.arch, include.os, include.runner, include.target, os, python-version, rocky-version→ 10, 3.11, 3.8, 8, 9, aarch64, linux, linux-libc, macos, macos-14, macos-aarch64, ubuntu-latest, windows, windows-latest, windows-x86_64, x86-64
Actions
conda-incubator/setup-miniconda
Commands
  • apt-get update && apt-get install -y python3 python3-pip python3-venv python3-debian
  • chmod +x ./uv
  • echo $(which python3)
  • python3 scripts/check_system_python.py --uv ./uv --externally-managed
  • ./uv run --no-project -p python3 -v python -c "import debian" ./uv run --no-project -p python3 -v --with anyio python -c "import debian"
  • dnf install python3 which -y && python3 -m ensurepip
  • chmod +x ./uv
  • echo $(which python3)
View raw YAML
on:
  workflow_call:
    inputs:
      sha:
        description: "The commit SHA to use for artifact names"
        required: true
        type: string

permissions: {}

jobs:
  system-test-debian:
    timeout-minutes: 10
    name: "python on debian"
    runs-on: ubuntu-latest
    container: debian:trixie@sha256:5cf544fad978371b3df255b61e209b373583cb88b733475c86e49faa15ac2104
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install Python"
        run: apt-get update && apt-get install -y python3 python3-pip python3-venv python3-debian

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv --externally-managed

      - name: "Test `uv run` with system Python"
        run: |
          ./uv run --no-project -p python3 -v python -c "import debian"
          ./uv run --no-project -p python3 -v --with anyio python -c "import debian"

  system-test-fedora:
    timeout-minutes: 10
    name: "python on fedora"
    runs-on: ubuntu-latest
    container: fedora:45@sha256:af8cdc432037f5e8e288bbc26c2b55b96000a911ec5b37959cd464d830f6cc5b
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install Python"
        run: dnf install python3 which -y && python3 -m ensurepip

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv

  system-test-ubuntu:
    timeout-minutes: 10
    name: "python3.12 via setup-python"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.12"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate global Python install"
        run: python scripts/check_system_python.py --uv ./uv

  system-test-python-36:
    timeout-minutes: 10
    name: "python3.6 on debian buster"
    runs-on: ubuntu-latest
    container: python:3.6-buster
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv

  system-test-python-37:
    timeout-minutes: 10
    name: "python3.7 on debian buster"
    runs-on: ubuntu-latest
    container: python:3.7-buster
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv

  # Currently failing, see https://github.com/astral-sh/uv/issues/13811
  # system-test-opensuse:
  #   timeout-minutes: 5
  #   name: "python on opensuse"
  #   runs-on: ubuntu-latest
  #   container: opensuse/tumbleweed
  #   steps:
  #     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

  #     - name: "Install Python"
  #       run: >
  #         until
  #         zypper install -y python310 which && python3.10 -m ensurepip && mv /usr/bin/python3.10 /usr/bin/python3;
  #         do sleep 10;
  #         done

  #         # We retry because `zypper` can fail during remote repository updates
  #         # The above will not sleep forever due to the job level timeout

  #     - name: "Download binary"
  #       uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
  #       with:
  #         name: uv-linux-libc-${{ inputs.sha }}

  #     - name: "Prepare binary"
  #       run: chmod +x ./uv

  #     - name: "Print Python path"
  #       run: echo $(which python3)

  #     - name: "Validate global Python install"
  #       run: python3 scripts/check_system_python.py --uv ./uv

  # Note: rockylinux is a 1-1 code compatible distro to rhel
  # rockylinux mimics centos but with added maintenance stability
  # and avoids issues with centos stream uptime concerns
  system-test-rocky-linux:
    timeout-minutes: 10
    name: "python on rocky linux ${{ matrix.rocky-version }}"
    runs-on: ubuntu-latest
    container: rockylinux/rockylinux:${{ matrix.rocky-version }}
    strategy:
      fail-fast: false
      matrix:
        rocky-version: ["8", "9", "10"]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install Python"
        if: matrix.rocky-version == '8'
        run: |
          for i in {1..5}; do
            dnf install python39 python39-pip which -y && break || { echo "Attempt $i failed, retrying in 10 seconds..."; sleep 10; }
            if [ $i -eq 5 ]; then
              echo "Failed to install Python after 5 attempts"
              exit 1
            fi
          done

      - name: "Install Python"
        if: matrix.rocky-version == '9'
        run: |
          for i in {1..5}; do
            dnf install python3.9 python3.9-pip which -y && break || { echo "Attempt $i failed, retrying in 10 seconds..."; sleep 10; }
            if [ $i -eq 5 ]; then
              echo "Failed to install Python after 5 attempts"
              exit 1
            fi
          done

      - name: "Install Python"
        if: matrix.rocky-version == '10'
        run: |
          dnf install python3 python3-pip which -y

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      # Needed for building Pydantic
      - name: "Install build tools"
        run: dnf install -y gcc

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv

  system-test-graalpy:
    timeout-minutes: 10
    name: "graalpy on linux"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "graalpy24.1"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which graalpy)

      - name: "Validate global Python install"
        run: graalpy scripts/check_system_python.py --uv ./uv

  system-test-pypy:
    timeout-minutes: 10
    name: "pypy on linux"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "pypy3.9"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which pypy)

      - name: "Validate global Python install"
        run: pypy scripts/check_system_python.py --uv ./uv

  system-test-pyston:
    timeout-minutes: 10
    name: "pyston on linux"
    runs-on: ubuntu-latest
    container: pyston/pyston:2.3.5
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which pyston)

      - name: "Validate global Python install"
        run: pyston scripts/check_system_python.py --uv ./uv

  system-test-chainguard-dev:
    timeout-minutes: 10
    name: "python on chainguard-dev"
    runs-on: ubuntu-latest
    container:
      image: cgr.dev/chainguard/python:latest-dev@sha256:197dc1b7c30dc150d8f8e7c4f9d314674ea41a95602f97fd4a4cd84d7fa7f480
      options: --user root --entrypoint /bin/sh
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate global Python install"
        run: python scripts/check_system_python.py --uv ./uv

  # The Chainguard distroless image has no shell, so we can't use `container:`
  # directly. Instead, we run on ubuntu-latest and use `docker run` to execute
  # inside the image.
  system-test-chainguard:
    timeout-minutes: 10
    name: "python on chainguard"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Validate global Python install"
        run: |
          docker run --rm --user root \
            -v "${{ github.workspace }}:/app" \
            -w /app \
            cgr.dev/chainguard/python:latest@sha256:c789723622cfc4a6b5d604a59250e3f708d0b4bb64cabb39a17c47119a224179 \
            scripts/check_system_python.py --uv ./uv

  system-test-alpine:
    timeout-minutes: 10
    name: "python on alpine"
    runs-on: ubuntu-latest
    container: alpine:latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install Python"
        run: apk add --update --no-cache python3 py3-pip

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv --externally-managed

  system-test-macos-aarch64:
    timeout-minutes: 10
    name: "python on macos aarch64"
    runs-on: macos-14 # github-macos-14-aarch64-3
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-macos-aarch64-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv --externally-managed

  system-test-macos-aarch64-homebrew:
    timeout-minutes: 10
    name: "homebrew python on macos aarch64"
    runs-on: macos-14 # github-macos-14-aarch64-3
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install Python"
        run: brew install python3

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-macos-aarch64-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv --externally-managed

  system-test-macos-aarch64-emulated:
    timeout-minutes: 10
    name: "x86-64 python on macos aarch64"
    runs-on: macos-14 # github-macos-14-aarch64-3
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: 3.13
          architecture: x64

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-macos-aarch64-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv --externally-managed

  system-test-macos-x86_64:
    timeout-minutes: 10
    name: "python on macos x86-64"
    runs-on: macos-15-intel # github-macos-15-x86_64-4
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      # We test with GitHub's Python as a regression test for
      # https://github.com/astral-sh/uv/issues/2450
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-macos-x86_64-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv --externally-managed

  system-test-windows-python-310:
    timeout-minutes: 10
    name: "python3.10 on windows x86-64"
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.10"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate global Python install"
        run: py -3.10 ./scripts/check_system_python.py --uv ./uv.exe

  system-test-windows-x86_64-python-310:
    timeout-minutes: 10
    name: "python3.10 on windows x86"
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.10"
          architecture: "x86"

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate global Python install"
        run: python ./scripts/check_system_python.py --uv ./uv.exe

  system-test-windows-x86_64-python-313:
    timeout-minutes: 10
    name: "python3.13 on windows x86-64"
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.13"
          allow-prereleases: true

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate global Python install"
        run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe

  system-test-windows-aarch64-x86-python-313:
    timeout-minutes: 10
    name: "x86-64 python3.13 on windows aarch64"
    runs-on: windows-11-arm
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.13"
          architecture: "x64"
          allow-prereleases: true

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-aarch64-${{ inputs.sha }}

      - name: "Validate global Python install"
        run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe

  system-test-windows-aarch64-aarch64-python-313:
    timeout-minutes: 10
    name: "aarch64 python3.13 on windows aarch64"
    runs-on: windows-11-arm
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.13"
          architecture: "arm64"
          allow-prereleases: true

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-aarch64-${{ inputs.sha }}

      - name: "Validate global Python install"
        run: py -3.13-arm64 ./scripts/check_system_python.py --uv ./uv.exe

  system-test-choco:
    timeout-minutes: 10
    name: "python3.9 via chocolatey"
    runs-on: windows-latest
    env:
      TEST_PYTHON_VERSION: "3.9.13"
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install Python"
        run: choco install python3 --verbose --version=$env:TEST_PYTHON_VERSION

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      # Chocolatey updates the registry but doesn't update `GITHUB_PATH` and since figuring out what
      # it added would be more work than it's worth, it's simpler to just reload the values from the
      # registry.
      - name: "Validate global Python install"
        run: |
          $env:PATH = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
          python ./scripts/check_system_python.py --uv ./uv.exe --check-python-version $env:TEST_PYTHON_VERSION --check-path

  system-test-pyenv:
    timeout-minutes: 10
    name: "python3.9 via pyenv"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Install pyenv"
        run: |
          # Install pyenv
          curl https://pyenv.run | bash

          # Set up environment variables for current step
          export PYENV_ROOT="$HOME/.pyenv"
          export PATH="$PYENV_ROOT/bin:$PATH"
          eval "$(pyenv init -)"

          # Install Python 3.9
          pyenv install 3.9
          pyenv global 3.9

          # Make environment variables persist across steps
          echo "PYENV_ROOT=$HOME/.pyenv" >> $GITHUB_ENV
          echo "$HOME/.pyenv/bin" >> $GITHUB_PATH
          echo "$HOME/.pyenv/shims" >> $GITHUB_PATH

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3.9)

      - name: "Validate global Python install"
        run: python3.9 scripts/check_system_python.py --uv ./uv

  system-test-linux-313:
    timeout-minutes: 10
    name: "python3.13 via setup-python"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: 3.13
          allow-prereleases: true

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-libc-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3.13)

      - name: "Validate global Python install"
        run: python3.13 scripts/check_system_python.py --uv ./uv

  system-test-conda:
    timeout-minutes: 10
    name: "conda${{ matrix.python-version }} on ${{ matrix.os }} ${{ matrix.arch }}"
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        os: ["linux", "windows", "macos"]
        python-version: ["3.8", "3.11"]
        include:
          - {
              os: "linux",
              target: "linux-libc",
              runner: "ubuntu-latest",
              arch: "x86-64",
            }
          - {
              os: "windows",
              target: "windows-x86_64",
              runner: "windows-latest",
              arch: "x86-64",
            }
          - {
              os: "macos",
              target: "macos-aarch64",
              runner: "macos-14",
              arch: "aarch64",
            }
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167 # v3.3.0
        with:
          miniconda-version: "latest"
          activate-environment: uv
          python-version: ${{ matrix.python-version }}

      - name: Conda info
        shell: bash -el {0}
        run: conda info

      - name: Conda list
        shell: pwsh
        run: conda list

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-${{ matrix.target }}-${{ inputs.sha }}

      - name: "Prepare binary"
        if: ${{ matrix.os != 'windows' }}
        run: chmod +x ./uv

      - name: "Print Python path"
        shell: bash -el {0}
        run: echo $(which python)

      - name: "Validate global Python install"
        shell: bash -el {0}
        run: python ./scripts/check_system_python.py --uv ./uv

  system-test-amazonlinux:
    timeout-minutes: 10
    name: "python on amazonlinux"
    runs-on: ubuntu-latest
    container: amazonlinux:2023
    steps:
      - name: "Install base requirements"
        run: |
          # Needed for `actions/checkout`
          yum install tar gzip which -y
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install Python"
        run: yum install python3 python3-pip -y

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: Install build tools
        run: yum install -y gcc

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv

  system-test-windows-embedded-python-310:
    timeout-minutes: 10
    name: "embedded python3.10 on windows x86-64"
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      # Download embedded Python.
      - name: "Download embedded Python"
        run: curl -LsSf https://www.python.org/ftp/python/3.11.8/python-3.11.8-embed-amd64.zip -o python-3.11.8-embed-amd64.zip

      - name: "Unzip embedded Python"
        run: 7z x python-3.11.8-embed-amd64.zip -oembedded-python

      - name: "Show embedded Python contents"
        run: ls embedded-python

      - name: "Set PATH"
        run: echo "${{ github.workspace }}\embedded-python" >> $env:GITHUB_PATH

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate embedded Python install"
        run: python ./scripts/check_embedded_python.py --uv ./uv.exe
test-windows-trampolines matrix perms .github/workflows/test-windows-trampolines.yml
Triggers
workflow_call
Runs on
ubuntu-latest, windows-latest, ${{ matrix.runner }}
Jobs
windows-version-check, windows-trampoline-check, windows-trampoline-test
Matrix
include, include.runner, include.target-arch, target-arch→ aarch64, i686, windows-11-arm, windows-latest, x86_64
Actions
astral-sh/setup-uv, Swatinem/rust-cache, taiki-e/install-action, Swatinem/rust-cache
Commands
  • uv run --no-project scripts/check-trampoline-version-consistency.py
  • ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1
  • Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse
  • rustup target add ${{ matrix.target-arch }}-pc-windows-msvc rustup component add rust-src --target ${{ matrix.target-arch }}-pc-windows-msvc
  • cargo fmt --check
  • cargo clippy --all-features --locked --target x86_64-pc-windows-msvc --tests -- -D warnings
  • $output = cargo bloat --release --target x86_64-pc-windows-msvc $filteredOutput = $output | Select-String -Pattern 'core::fmt::write|core::fmt::getcount' -NotMatch $containsPatterns = $filteredOutput | Select-String -Pattern 'core::fmt|std::panicking|std::backtrace_rs' if ($containsPatterns) { Exit 1 } else { Exit 0 }
  • ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1
View raw YAML
on:
  workflow_call:

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  # Verify windows crate version matches between workspaces
  windows-version-check:
    timeout-minutes: 5
    runs-on: ubuntu-latest
    name: "check windows crate version"
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0

      - name: "Check windows crate versions match"
        run: uv run --no-project scripts/check-trampoline-version-consistency.py

  # Separate jobs for the nightly crate
  windows-trampoline-check:
    timeout-minutes: 15
    runs-on: windows-latest
    name: "check on ${{ matrix.target-arch }}"
    strategy:
      fail-fast: false
      matrix:
        target-arch: ["x86_64", "i686", "aarch64"]
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup Dev Drive
        run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1

      # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
      - name: Copy Git Repo to Dev Drive
        run: |
          Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline

      - name: "Install Rust toolchain"
        working-directory: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
        run: |
          rustup target add ${{ matrix.target-arch }}-pc-windows-msvc
          rustup component add rust-src --target ${{ matrix.target-arch }}-pc-windows-msvc

      - name: "Install cargo-bloat"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-bloat

      - name: "rustfmt"
        working-directory: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
        run: cargo fmt --check

      - name: "Clippy"
        working-directory: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
        run: cargo clippy --all-features --locked --target x86_64-pc-windows-msvc --tests -- -D warnings

      - name: "Bloat Check"
        working-directory: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
        run: |
          $output = cargo bloat --release --target x86_64-pc-windows-msvc
          $filteredOutput = $output | Select-String -Pattern 'core::fmt::write|core::fmt::getcount' -NotMatch
          $containsPatterns = $filteredOutput | Select-String -Pattern 'core::fmt|std::panicking|std::backtrace_rs'

          if ($containsPatterns) {
              Exit 1
          } else {
              Exit 0
          }

  # Separate jobs for the nightly crate
  windows-trampoline-test:
    timeout-minutes: 10
    runs-on: ${{ matrix.runner }}
    name: "test on ${{ matrix.target-arch }}"
    strategy:
      fail-fast: false
      matrix:
        include:
          - { runner: windows-latest, target-arch: "x86_64" }
          - { runner: windows-latest, target-arch: "i686" }
          - { runner: windows-11-arm, target-arch: "aarch64" }
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: Setup Dev Drive
        run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1
      # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone...
      - name: Copy Git Repo to Dev Drive
        run: |
          Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
      - name: "Install Rust toolchain"
        working-directory: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
        run: |
          rustup target add ${{ matrix.target-arch }}-pc-windows-msvc
          rustup component add rust-src --target ${{ matrix.target-arch }}-pc-windows-msvc
      - name: "Test committed binaries"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: |
          rustup target add ${{ matrix.target-arch }}-pc-windows-msvc
          cargo test -p uv-trampoline-builder --target ${{ matrix.target-arch }}-pc-windows-msvc
      # Build and copy the new binaries
      - name: "Build"
        working-directory: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
        run: |
          cargo build --target ${{ matrix.target-arch }}-pc-windows-msvc
          cp target/${{ matrix.target-arch }}-pc-windows-msvc/debug/uv-trampoline-console.exe ../uv-trampoline-builder/trampolines/uv-trampoline-${{ matrix.target-arch }}-console.exe
          cp target/${{ matrix.target-arch }}-pc-windows-msvc/debug/uv-trampoline-gui.exe ../uv-trampoline-builder/trampolines/uv-trampoline-${{ matrix.target-arch }}-gui.exe
      - name: "Test new binaries"
        working-directory: ${{ env.UV_WORKSPACE }}
        run: |
          # We turn off the default "production" test feature since these are debug binaries
          cargo test -p uv-trampoline-builder --target ${{ matrix.target-arch }}-pc-windows-msvc --no-default-features