Files
uv/.github/workflows/ci.yml
Zanie Blue 903dd292b3 Split up ci.yml (#17388)
This file is too big for an LLM context window and several contributors
have complained about it being too scary to touch.

This also gets us collapsible sections in the UI.

I renamed some jobs for clarity in the meantime. And added a meta-job
for required checks passing so we can avoid churn in our "Settings" when
we change job names.

Note this was entirely refactored by Claude.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 12:34:30 -06:00

294 lines
11 KiB
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: ubuntu-latest
outputs:
# Run checks/tests if no-test label is not present and code changed (or on main)
test-code: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (steps.changed.outputs.code_any_changed == 'true' || github.ref == 'refs/heads/main') }}
# Run schema check if schema file changed
check-schema: ${{ steps.changed.outputs.schema_changed == 'true' }}
# Run release build test if release files changed
build-release-binaries: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && steps.changed.outputs.release_build_changed == 'true' }}
# Run format/lint checks (always unless no-test label)
run-checks: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') }}
# Run publish test if publish-related files changed
test-publish: ${{ steps.changed.outputs.publish_changed == 'true' || github.ref == 'refs/heads/main' }}
# Run trampoline checks if trampoline-related code changed
test-windows-trampoline: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (steps.changed.outputs.trampoline_any_changed == 'true' || github.ref == 'refs/heads/main') }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: "Determine changed files"
id: changed
shell: bash
run: |
CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha || 'origin/main' }}...HEAD)
CODE_CHANGED=false
SCHEMA_CHANGED=false
RELEASE_BUILD_CHANGED=false
PUBLISH_CHANGED=false
TRAMPOLINE_CHANGED=false
while IFS= read -r file; do
# Check if the schema file changed (e.g., in a release PR)
if [[ "${file}" == "uv.schema.json" ]]; then
echo "Detected schema change: ${file}"
SCHEMA_CHANGED=true
fi
# Check if release build files changed (pyproject.toml, Cargo.toml, etc.)
if [[ "${file}" == "pyproject.toml" || "${file}" == "Cargo.toml" || "${file}" == "Cargo.lock" || "${file}" == "rust-toolchain.toml" || "${file}" == ".cargo/config.toml" || "${file}" == "crates/uv-build/Cargo.toml" || "${file}" == "crates/uv-build/pyproject.toml" || "${file}" == ".github/workflows/build-release-binaries.yml" ]]; then
echo "Detected release build change: ${file}"
RELEASE_BUILD_CHANGED=true
fi
# Check if publish-related files changed
if [[ "${file}" =~ ^crates/uv-publish/ || "${file}" =~ ^scripts/publish/ || "${file}" == ".github/workflows/ci.yml" ]]; then
echo "Detected publish change: ${file}"
PUBLISH_CHANGED=true
fi
# Check if trampoline-related files changed
if [[ "${file}" =~ ^crates/uv-trampoline/ ]] || [[ "${file}" =~ ^crates/uv-trampoline-builder/ ]]; then
echo "Detected trampoline change: ${file}"
TRAMPOLINE_CHANGED=true
fi
if [[ "${file}" =~ ^docs/ ]]; then
echo "Skipping ${file} (matches docs/ pattern)"
continue
fi
if [[ "${file}" =~ ^mkdocs.*\.yml$ ]]; then
echo "Skipping ${file} (matches mkdocs*.yml pattern)"
continue
fi
if [[ "${file}" =~ \.md$ ]]; then
echo "Skipping ${file} (matches *.md pattern)"
continue
fi
if [[ "${file}" =~ ^bin/ ]]; then
echo "Skipping ${file} (matches bin/ pattern)"
continue
fi
if [[ "${file}" =~ ^assets/ ]]; then
echo "Skipping ${file} (matches assets/ pattern)"
continue
fi
echo "Detected code change in: ${file}"
CODE_CHANGED=true
done <<< "${CHANGED_FILES}"
echo "code_any_changed=${CODE_CHANGED}" >> "${GITHUB_OUTPUT}"
echo "schema_changed=${SCHEMA_CHANGED}" >> "${GITHUB_OUTPUT}"
echo "release_build_changed=${RELEASE_BUILD_CHANGED}" >> "${GITHUB_OUTPUT}"
echo "publish_changed=${PUBLISH_CHANGED}" >> "${GITHUB_OUTPUT}"
echo "trampoline_any_changed=${TRAMPOLINE_CHANGED}" >> "${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 }}
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-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 }}
test:
needs: plan
if: ${{ needs.plan.outputs.test-code == 'true' }}
uses: ./.github/workflows/test.yml
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
test-smoke:
needs: build-dev-binaries
uses: ./.github/workflows/test-smoke.yml
with:
sha: ${{ github.sha }}
test-integration:
needs: build-dev-binaries
uses: ./.github/workflows/test-integration.yml
secrets: inherit
permissions:
id-token: write
with:
sha: ${{ github.sha }}
test-system:
needs: build-dev-binaries
uses: ./.github/workflows/test-system.yml
with:
sha: ${{ github.sha }}
test-ecosystem:
needs: build-dev-binaries
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
bench:
needs: plan
if: ${{ needs.plan.outputs.test-code == 'true' }}
uses: ./.github/workflows/bench.yml
secrets: inherit
# This job cannot be moved into a reusable workflow because it includes coverage for uploading
# attestations and PyPI does not support attestations in reusable workflows.
test-publish:
name: "test uv publish"
timeout-minutes: 20
needs:
- plan
- build-dev-binaries
runs-on: ubuntu-latest
# Only the main repository is a trusted publisher
if: ${{ github.repository == 'astral-sh/uv' && github.event.pull_request.head.repo.fork != true && needs.plan.outputs.test-publish == 'true' }}
environment: uv-test-publish
env:
# No dbus in GitHub Actions
PYTHON_KEYRING_BACKEND: keyrings.alt.file.PlaintextKeyring
PYTHON_VERSION: 3.12
permissions:
# For trusted publishing
id-token: write
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "${{ env.PYTHON_VERSION }}"
- name: "Download binary"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: uv-linux-libc-${{ github.sha }}
- name: "Prepare binary"
run: chmod +x ./uv
- name: "Build astral-test-pypa-gh-action"
run: |
# Build a yet unused version of `astral-test-pypa-gh-action`
mkdir astral-test-pypa-gh-action
cd astral-test-pypa-gh-action
../uv init --package
# Get the latest patch version
patch_version=$(curl https://test.pypi.org/simple/astral-test-pypa-gh-action/?format=application/vnd.pypi.simple.v1+json | jq --raw-output '.files[-1].filename' | sed 's/astral_test_pypa_gh_action-0\.1\.\([0-9]\+\)\.tar\.gz/\1/')
# Set the current version to one higher (which should be unused)
sed -i "s/0.1.0/0.1.$((patch_version + 1))/g" pyproject.toml
../uv build
- name: "Publish astral-test-pypa-gh-action"
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
# With this GitHub action, we can't do as rigid checks as with our custom Python script, so we publish more
# leniently
skip-existing: "true"
verbose: "true"
repository-url: "https://test.pypi.org/legacy/"
packages-dir: "astral-test-pypa-gh-action/dist"
- name: "Add password to keyring"
run: |
# `keyrings.alt` contains the plaintext keyring
./uv tool install --with keyrings.alt keyring
echo $UV_TEST_PUBLISH_KEYRING | keyring set https://test.pypi.org/legacy/?astral-test-keyring __token__
env:
UV_TEST_PUBLISH_KEYRING: ${{ secrets.UV_TEST_PUBLISH_KEYRING }}
- name: "Add password to uv text store"
run: |
./uv auth login https://test.pypi.org/legacy/?astral-test-text-store --token ${UV_TEST_PUBLISH_TEXT_STORE}
env:
UV_TEST_PUBLISH_TEXT_STORE: ${{ secrets.UV_TEST_PUBLISH_TEXT_STORE }}
- name: "Publish test packages"
# `-p 3.12` prefers the python we just installed over the one locked in `.python_version`.
run: ./uv run -p "${PYTHON_VERSION}" scripts/publish/test_publish.py --uv ./uv all
env:
RUST_LOG: uv=debug,uv_publish=trace
UV_TEST_PUBLISH_TOKEN: ${{ secrets.UV_TEST_PUBLISH_TOKEN }}
UV_TEST_PUBLISH_PASSWORD: ${{ secrets.UV_TEST_PUBLISH_PASSWORD }}
UV_TEST_PUBLISH_GITLAB_PAT: ${{ secrets.UV_TEST_PUBLISH_GITLAB_PAT }}
UV_TEST_PUBLISH_CODEBERG_TOKEN: ${{ secrets.UV_TEST_PUBLISH_CODEBERG_TOKEN }}
UV_TEST_PUBLISH_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_PUBLISH_CLOUDSMITH_TOKEN }}
UV_TEST_PUBLISH_PYX_TOKEN: ${{ secrets.UV_TEST_PUBLISH_PYX_TOKEN }}
UV_TEST_PUBLISH_PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
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-latest
steps:
- name: "Check required jobs passed"
run: echo '${{ toJSON(needs) }}' | jq -e 'all(.[]; .result == "success" or .result == "skipped")' > /dev/null