Files
uv/.github/workflows/ci.yml
Zanie Blue 7fa30ab278 Move release plan checks to a separate workflow (#17422)
We get a bunch of redundant skipped `Release / Build binary ...` jobs in
CI otherwise, and I would rather the release workflow didn't have a pull
request trigger at all.

Follows #17388
2026-01-12 14:49:40 -06:00

299 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-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 }}
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