Skip to content

Latest commit

 

History

History
208 lines (179 loc) · 17.1 KB

README.md

File metadata and controls

208 lines (179 loc) · 17.1 KB

GitHub workflows

Reusable workflows and composite Actions shared by the tox-dev repositories. They run tox and CodeQL, check pull request news, rehearse and prepare releases, push App-authored release commits and tags, publish GitHub Releases, JetBrains plugins, pre-commit mirrors and LaTeX PDFs, and keep dependencies current. Each repository keeps its own dispatch form, publisher, GitHub environments, and required check names; the shared pieces hold the steps every repository repeats and the checks that guard its keys.

Two rules run through all of them:

  • Keys stay away from project code. Jobs that run hooks, tests, or builds hold read-only tokens. A writer job gets the App key or contents: write, checks the patch or artifact it was handed, and refuses pull_request, pull_request_target, workflow_run, issue_comment, and merge_group triggers. Writer jobs run no project code, except the pre-commit mirror, which runs the repository's hooks and refuses those triggers for that reason.
  • Tools arrive cooled. Release tools install hash-checked from the caller's pylock.toml; tools the Actions need for themselves install from PyPI only once a release is seven days old, by default. See Locked tools and cutoffs.

What is here

Reusable workflows, called with uses: tox-dev/github-workflows/.github/workflows/<file>@SHA. The caller job must grant at least the listed permissions; "App key" means it also passes the RELEASE_APP_PRIVATE_KEY secret.

Workflow Purpose Caller grants Docs
check.yaml Run tox environments, one job per row, with a gate. contents: read PR checks
cooled-hooks.yaml Run every prek hook over the tree, environments built under the cutoff. contents: read PR checks
prepare-towncrier-release.yaml Build the Towncrier changelog, push the release commit and tag; or dry run. contents: read, pull-requests: read; App key Prepare
prepare-release-creator.yaml Same for multi-package or Rust repositories with a caller-assembled changelog. contents: read, pull-requests: read; App key Prepare
validate-python-release.yaml Build a wheel and sdist from the tag against locked build constraints. contents: read Publish
build-maturin-wheels.yaml Build maturin wheels for a matrix, plus the sdist. contents: read Publish
release-notes.yaml Render the GitHub Release body from the changelog. contents: read Publish
github-release.yaml Verify the PyPI files, then publish the GitHub Release. contents: write Publish
upgrade-release-tools.yaml Refresh pylock.toml and open a pull request. contents: read; App key Maintenance
update-python-dependencies.yaml Bump dependency floors, relock, and open a pull request. contents: read; App key Maintenance
open-maintenance-pr.yaml Turn a patch from a read-only job into one pull request. contents: read; App key Maintenance
codeql.yaml CodeQL analysis, one job per language. contents: read, security-events: write Security
scorecard.yaml OpenSSF Scorecard analysis, published and uploaded to code scanning. eight scopes, listed on its page Security
jetbrains-plugin-check.yaml Build, verify, and UI-test a JetBrains plugin. contents: read JetBrains
publish-pre-commit-mirror.yaml Mirror a Python package's hook as a pre-commit repository. contents: write pre-commit mirror
latex-to-pages.yaml Compile a LaTeX document and deploy the PDF to GitHub Pages. contents: write, pages: read LaTeX

Composite Actions, called with uses: tox-dev/github-workflows/<name>@SHA:

Action Purpose Docs
required-checks Gate job: fail unless every needed job succeeded. PR checks
validate-news Require a news fragment on human PRs; check fragments and lock changes. PR checks
build-python-distribution Keyless wheel and sdist build for PR jobs. PR checks
install-locked-tools Install selected groups from the caller's pylock.toml. Locked tools
export-build-constraints Write hashed build constraints from a lock. Locked tools
refresh-release-lock Refresh or check the Nab lock. Locked tools
check-nab-exceptions Fail on an undated or expired cutoff exception. Locked tools
cooled-prek Run prek with hook environments under the seven-day cutoff. Locked tools
read-towncrier-config Read changelog path, fragment directory, and format from HEAD. Prepare
prepare-towncrier Select the version, build and format the changelog, write a patch. Prepare
validate-release-patch Apply a release patch and check its paths and modes. Prepare
commit-and-tag Commit as the App bot and push main and the tag atomically. Prepare
render-release-notes Render the Release body to a file. Publish
select-previous-tag Find the preceding release tag. Publish
reconcile-pypi-artifacts Compare built files with PyPI and verify their attestations. Publish
publish-github-release Create, resume, or verify a GitHub Release with its assets. Publish
sign-jetbrains-plugin Sign a plugin ZIP in a no-checkout job. JetBrains
stage-jetbrains-marketplace Check a signed ZIP against the Marketplace before and after upload. JetBrains
publish-jetbrains-marketplace Upload a signed ZIP once per tag, with a receipt. JetBrains
prepare-jetbrains-post-release Prepare the next -dev version commit without a token. JetBrains
publish-post-release-pr Push a prepared commit and open its pull request. JetBrains

Quick start

A typical Python library wires the pieces up with the caller files below. Read Release flow for how they connect and the one-time setup: a repository-only release App, the release-auth and release environments, and the lock groups.

.github/workflows/check.yaml runs the tests and the release dry run, and holds the required gate:

name: 🧪 check
on:
  pull_request:
    types: [opened, synchronize, reopened]
  push:
    branches: ["main"]
    tags-ignore: ["**"]
permissions: {}
concurrency:
  group: check-${{ github.ref }}
  cancel-in-progress: true
jobs:
  test:
    name: 🧪 Test
    uses: tox-dev/github-workflows/.github/workflows/check.yaml@SHA # 6.6.0
    permissions:
      contents: read
    with:
      include: |
        [{"env": "3.15"}, {"env": "3.14", "python": "3.14"}, {"env": "type"}, {"env": "pkg_meta"}]
  release:
    name: 🏷️ Release dry run
    uses: ./.github/workflows/prepare-release.yaml # zizmor: ignore[self-repository] actionlint rejects the $/ form
    permissions:
      contents: read
      pull-requests: read
    with:
      dry-run: true
  gate:
    name: 🚦 Required checks pass
    if: ${{ !cancelled() }}
    needs: [test, release]
    runs-on: ubuntu-26.04
    permissions: {}
    steps:
      - uses: tox-dev/github-workflows/required-checks@SHA # 6.6.0
        with:
          needs: ${{ toJSON(needs) }}

The release job references the caller's own workflow with ./ so that actionlint accepts it; a repository that does not run actionlint can write $/.github/workflows/prepare-release.yaml instead, see Dry run on pull requests.

.github/workflows/prepare-release.yaml is dispatched by hand to release, and called by the gate as a dry run:

name: Prepare release
on:
  workflow_dispatch:
    inputs:
      bump:
        type: choice
        options: [auto, major, minor, patch]
        default: auto
  workflow_call:
    inputs:
      dry-run:
        type: boolean
        required: true
permissions: {}
jobs:
  prepare:
    uses: tox-dev/github-workflows/.github/workflows/prepare-towncrier-release.yaml@SHA # 6.6.0
    permissions:
      contents: read
      pull-requests: read
    with:
      bump: ${{ inputs.bump || 'auto' }}
      dry-run: ${{ inputs.dry-run == true }}
    secrets:
      RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

The remaining files, each shown in full on its page:

Caller file Calls Example
news.yaml (on labeled/unlabeled too) install-locked-tools, validate-news PR checks
release.yaml (on tag push) validate-python-release, release-notes, PyPI, github-release Publish
update-dependencies.yaml (weekly) update-python-dependencies or upgrade-release-tools Maintenance
codeql.yaml, scorecard.yaml (weekly, push) codeql, scorecard Security

Require two checks in the main ruleset: the gate (🚦 Required checks pass) and the news job.

Versioning and pinning

Releases are tagged X.Y.Z with no v prefix and no moving major tag such as v6. Each is listed in the changelog. Pin every workflow and Action from this repository to the full commit SHA of a release tag, with the version in a trailing comment:

uses: tox-dev/github-workflows/.github/workflows/check.yaml@2a3b863f4563ce9891d2cee1ed381b6f82654836 # 6.6.0

A full SHA is the only reference GitHub cannot move, as its security guidance for third-party actions explains. SHA in the examples means that commit. Pin every piece to the same release: a reusable workflow runs the Actions from its own commit, so mixed pins mix versions.

Before moving a pin across releases, read each "Breaking changes" section you skip: 6.0.0, 5.0.0, 4.0.0, 3.0.1, 3.0.0, 2.0.0, and 1.0.0. Most breaking changes ship in a major release, but 3.0.1 shows that a patch release can carry one too.

What stays in your repository

The shared pieces leave these to each caller on purpose:

  • PyPI upload. Trusted publishing does not work from a reusable workflow, so the upload job lives in the caller's tag workflow.
  • Project-specific build steps, such as PGO, README generation, or vendoring, beyond the documented pre- and post-build commands.
  • Required check names, triggers, and schedules. Callers name their gate and schedule their own runs.
  • Cutoff exceptions. The caller adds and removes [tool.nab] exceptions; the shared checks only enforce their expiry dates.

Documentation

See Contributing for local checks and news fragments.