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