ci workflow (samuelcolvin/watchfiles)
The ci workflow from samuelcolvin/watchfiles, explained and optimized by Latchkey.
CI health: F - at risk
Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the ci workflow from the samuelcolvin/watchfiles repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: ci
on:
push:
branches:
- main
tags:
- "**"
pull_request: {}
env:
COLUMNS: 120
UV_PYTHON: 3.12
UV_FROZEN: "1"
jobs:
test:
name: test ${{ matrix.python-version }}, rust ${{ matrix.rust-version }} on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu, macos, windows]
rust-version: [stable, "1.83"]
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
- "3.14t"
- "3.15"
- "pypy3.11"
exclude:
- rust-version: "1.83"
os: macos
- rust-version: "1.83"
os: windows
runs-on: ${{ matrix.os }}-latest
env:
UV_PYTHON: ${{ matrix.python-version }}
RUST: ${{ matrix.rust-version }}
OS: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: install rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust-version }}
override: true
- name: cache rust
uses: Swatinem/rust-cache@v1
- if: matrix.os == 'ubuntu'
run: |
mkdir -p ${{ github.workspace }}/protected
touch ${{ github.workspace }}/protected/test
sudo chown -R root:root ${{ github.workspace }}/protected
sudo chmod 700 ${{ github.workspace }}/protected
- run: uv run python -c 'import sys; print("free threading enable:", hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled())'
- run: make test
env:
WATCHFILES_TEST_PERMISSION_DENIED_PATH: ${{ github.workspace }}/protected
- run: uv run coverage xml
- uses: codecov/codecov-action@v1.0.13
with:
file: ./coverage.xml
env_vars: UV_PYTHON,RUST,OS
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: install rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: cache rust
uses: Swatinem/rust-cache@v1
- run: uv sync --group lint
- uses: pre-commit/action@v3.0.0
with:
extra_args: --all-files --verbose
env:
SKIP: no-commit-to-branch
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- run: uv sync --group docs
- run: make docs
- name: store docs site
uses: actions/upload-artifact@v4
with:
name: docs
path: site
build:
name: >
build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }})
if: "!contains(github.event.pull_request.labels.*.name, 'Quick Build')"
strategy:
fail-fast: false
matrix:
os: [ubuntu, macos, windows]
target: [x86_64, aarch64]
manylinux: [auto]
include:
- os: ubuntu
platform: linux
pypy: true
- os: macos
target: x86_64
pypy: true
- os: macos
target: aarch64
pypy: true
- os: windows
ls: dir
# 3.15 is omitted from the Windows interpreter lists: pyo3's `generate-import-lib`
# feature uses python3-dll-a, which (as bundled by pyo3 0.28.3) only supports
# CPython up to 3.14, so generating the 3.15 import library fails. Restore 3.15
# once pyo3 ships python3-dll-a >= 0.2.15. See:
# https://github.com/samuelcolvin/watchfiles/pull/369#issuecomment-4474026284
- os: windows
ls: dir
target: i686
python-architecture: x86
interpreter: 3.10 3.11 3.12 3.13 3.14
- os: windows
ls: dir
target: x86_64
interpreter: 3.10 3.11 3.12 3.13 3.14
- os: windows
ls: dir
target: aarch64
interpreter: 3.11 3.12 3.13 3.14
- os: ubuntu
platform: linux
target: i686
- os: ubuntu
platform: linux
target: armv7
- os: ubuntu
platform: linux
target: ppc64le
- os: ubuntu
platform: linux
target: s390x
- os: ubuntu
platform: linux
target: riscv64gc-unknown-linux-gnu
# musllinux
- os: ubuntu
platform: linux
target: x86_64
manylinux: musllinux_1_1
- os: ubuntu
platform: linux
target: aarch64
manylinux: musllinux_1_1
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v5
- name: set up python
uses: actions/setup-python@v6
with:
python-version: "3.13"
architecture: ${{ matrix.python-architecture || 'x64' }}
- name: check GITHUB_REF matches package version
uses: samuelcolvin/check-python-version@v4.1
if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.os == 'ubuntu' }}
with:
version_file_path: Cargo.toml
- name: build sdist
if: ${{ matrix.os == 'ubuntu' && matrix.target == 'x86_64' && matrix.manylinux == 'auto' }}
uses: PyO3/maturin-action@v1.49.3
with:
command: sdist
args: --out dist
- name: build wheels
uses: PyO3/maturin-action@v1.49.3
with:
target: ${{ matrix.target }}
manylinux: ${{ matrix.manylinux || 'auto' }}
args: --release --out dist --interpreter ${{ matrix.interpreter || '3.10 3.11 3.12 3.13 3.14 3.14t 3.15' }}
- name: build pypy wheels
if: ${{ matrix.pypy }}
uses: PyO3/maturin-action@v1.49.3
with:
target: ${{ matrix.target }}
manylinux: ${{ matrix.manylinux || 'auto' }}
args: --release --out dist --interpreter pypy3.11
- run: ${{ matrix.ls || 'ls -lh' }} dist/
- uses: actions/upload-artifact@v4
with:
name: pypi_files_${{ matrix.os }}_${{ matrix.target }}_${{ matrix.interpreter || 'all' }}_${{ matrix.manylinux }}
path: dist
list-pypi-files:
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: get dist artifacts
uses: actions/download-artifact@v4
with:
pattern: pypi_files_*
merge-multiple: true
path: dist
- name: list dist files
run: |
ls -lh dist/
echo "`ls dist | wc -l` files"
- name: extract and list sdist file
run: |
mkdir sdist-files
tar -xvf dist/*.tar.gz -C sdist-files
tree -a sdist-files
- name: extract and list wheel file
run: ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1
- name: extract and list wheel file
run: python -m zipfile --list `ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1`
# Used for branch protection checks, see https://github.com/marketplace/actions/alls-green#why
check:
if: always()
needs: [test, lint, docs]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
release:
needs: [build, check, docs]
if: "success() && startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: get dist artifacts
uses: actions/download-artifact@v4
with:
pattern: pypi_files_*
merge-multiple: true
path: dist
- name: get docs
uses: actions/download-artifact@v4
with:
name: docs
path: site
- name: Publish to PyPI
run: "uv publish --trusted-publishing always dist/*"
- name: publish docs
uses: JamesIves/github-pages-deploy-action@v4.7.2
with:
branch: gh-pages
folder: site
The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: ci on: push: branches: - main tags: - "**" pull_request: {} env: COLUMNS: 120 UV_PYTHON: 3.12 UV_FROZEN: "1" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: timeout-minutes: 30 name: test ${{ matrix.python-version }}, rust ${{ matrix.rust-version }} on ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu, macos, windows] rust-version: [stable, "1.83"] python-version: - "3.10" - "3.11" - "3.12" - "3.13" - "3.14" - "3.14t" - "3.15" - "pypy3.11" exclude: - rust-version: "1.83" os: macos - rust-version: "1.83" os: windows runs-on: ${{ matrix.os }}-latest env: UV_PYTHON: ${{ matrix.python-version }} RUST: ${{ matrix.rust-version }} OS: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: install rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust-version }} override: true - name: cache rust uses: Swatinem/rust-cache@v1 - if: matrix.os == 'ubuntu' run: | mkdir -p ${{ github.workspace }}/protected touch ${{ github.workspace }}/protected/test sudo chown -R root:root ${{ github.workspace }}/protected sudo chmod 700 ${{ github.workspace }}/protected - run: uv run python -c 'import sys; print("free threading enable:", hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled())' - run: make test env: WATCHFILES_TEST_PERMISSION_DENIED_PATH: ${{ github.workspace }}/protected - run: uv run coverage xml - uses: codecov/codecov-action@v1.0.13 with: file: ./coverage.xml env_vars: UV_PYTHON,RUST,OS lint: timeout-minutes: 30 runs-on: latchkey-small steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: install rust uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true components: rustfmt, clippy - name: cache rust uses: Swatinem/rust-cache@v1 - run: uv sync --group lint - uses: pre-commit/action@v3.0.0 with: extra_args: --all-files --verbose env: SKIP: no-commit-to-branch docs: timeout-minutes: 30 runs-on: latchkey-small steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 with: enable-cache: true - run: uv sync --group docs - run: make docs - name: store docs site uses: actions/upload-artifact@v4 with: name: docs path: site build: timeout-minutes: 30 name: > build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }}) if: "!contains(github.event.pull_request.labels.*.name, 'Quick Build')" strategy: fail-fast: false matrix: os: [ubuntu, macos, windows] target: [x86_64, aarch64] manylinux: [auto] include: - os: ubuntu platform: linux pypy: true - os: macos target: x86_64 pypy: true - os: macos target: aarch64 pypy: true - os: windows ls: dir # 3.15 is omitted from the Windows interpreter lists: pyo3's `generate-import-lib` # feature uses python3-dll-a, which (as bundled by pyo3 0.28.3) only supports # CPython up to 3.14, so generating the 3.15 import library fails. Restore 3.15 # once pyo3 ships python3-dll-a >= 0.2.15. See: # https://github.com/samuelcolvin/watchfiles/pull/369#issuecomment-4474026284 - os: windows ls: dir target: i686 python-architecture: x86 interpreter: 3.10 3.11 3.12 3.13 3.14 - os: windows ls: dir target: x86_64 interpreter: 3.10 3.11 3.12 3.13 3.14 - os: windows ls: dir target: aarch64 interpreter: 3.11 3.12 3.13 3.14 - os: ubuntu platform: linux target: i686 - os: ubuntu platform: linux target: armv7 - os: ubuntu platform: linux target: ppc64le - os: ubuntu platform: linux target: s390x - os: ubuntu platform: linux target: riscv64gc-unknown-linux-gnu # musllinux - os: ubuntu platform: linux target: x86_64 manylinux: musllinux_1_1 - os: ubuntu platform: linux target: aarch64 manylinux: musllinux_1_1 runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v5 - name: set up python uses: actions/setup-python@v6 with: cache: 'pip' python-version: "3.13" architecture: ${{ matrix.python-architecture || 'x64' }} - name: check GITHUB_REF matches package version uses: samuelcolvin/check-python-version@v4.1 if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.os == 'ubuntu' }} with: version_file_path: Cargo.toml - name: build sdist if: ${{ matrix.os == 'ubuntu' && matrix.target == 'x86_64' && matrix.manylinux == 'auto' }} uses: PyO3/maturin-action@v1.49.3 with: command: sdist args: --out dist - name: build wheels uses: PyO3/maturin-action@v1.49.3 with: target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux || 'auto' }} args: --release --out dist --interpreter ${{ matrix.interpreter || '3.10 3.11 3.12 3.13 3.14 3.14t 3.15' }} - name: build pypy wheels if: ${{ matrix.pypy }} uses: PyO3/maturin-action@v1.49.3 with: target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux || 'auto' }} args: --release --out dist --interpreter pypy3.11 - run: ${{ matrix.ls || 'ls -lh' }} dist/ - uses: actions/upload-artifact@v4 with: name: pypi_files_${{ matrix.os }}_${{ matrix.target }}_${{ matrix.interpreter || 'all' }}_${{ matrix.manylinux }} path: dist list-pypi-files: timeout-minutes: 30 needs: [build] runs-on: latchkey-small steps: - uses: actions/checkout@v4 - name: get dist artifacts uses: actions/download-artifact@v4 with: pattern: pypi_files_* merge-multiple: true path: dist - name: list dist files run: | ls -lh dist/ echo "`ls dist | wc -l` files" - name: extract and list sdist file run: | mkdir sdist-files tar -xvf dist/*.tar.gz -C sdist-files tree -a sdist-files - name: extract and list wheel file run: ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1 - name: extract and list wheel file run: python -m zipfile --list `ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1` # Used for branch protection checks, see https://github.com/marketplace/actions/alls-green#why check: timeout-minutes: 30 if: always() needs: [test, lint, docs] runs-on: latchkey-small steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} release: timeout-minutes: 30 needs: [build, check, docs] if: "success() && startsWith(github.ref, 'refs/tags/')" runs-on: latchkey-small environment: release permissions: id-token: write contents: write steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: get dist artifacts uses: actions/download-artifact@v4 with: pattern: pypi_files_* merge-multiple: true path: dist - name: get docs uses: actions/download-artifact@v4 with: name: docs path: site - name: Publish to PyPI run: "uv publish --trusted-publishing always dist/*" - name: publish docs uses: JamesIves/github-pages-deploy-action@v4.7.2 with: branch: gh-pages folder: site
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
9 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
This workflow runs 7 jobs (59 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.