mirror of
https://github.com/astral-sh/uv
synced 2026-01-22 05:50:25 -05:00
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>
294 lines
11 KiB
YAML
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
|