astral-sh/uv
25 workflows · maturity 67% · 14 patterns · GitHub ↗
Practices
✓ Matrix✓ Permissions○ Security scan○ AI review✓ Cache✓ Concurrency✓ Reusable workflows
Detected patterns
Security dimensions
Workflows (25)
bench perms .github/workflows/bench.yml
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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