Skip to content

Release v6.4.1

Release v6.4.1 #8440

Workflow file for this run

---
name: πŸš€ Build & release
# Read https://github.com/actions/runner/issues/491 for insights on complex workflow execution logic.
"on":
workflow_call:
secrets:
PYPI_TOKEN:
required: false
WORKFLOW_UPDATE_GITHUB_PAT:
required: false
outputs:
nuitka_matrix:
description: Nuitka build matrix
value: ${{ toJSON(fromJSON(jobs.metadata.outputs.metadata).nuitka_matrix) }}
# Targets are chosen so that all commits get a chance to have their package tested.
push:
branches:
- main
pull_request:
branches-ignore:
- prepare-release
- renovate/**
paths:
- repomatic/**
- tests/**
- pyproject.toml
- uv.lock
- .github/workflows/release.yaml
concurrency:
# Release commits get a unique SHA-based group so they can never be cancelled.
# See repomatic/github/actions.py for rationale.
group: >-
${{ github.workflow }}-${{
github.event.pull_request.number
|| (
(startsWith(github.event.head_commit.message, '[changelog] Release')
|| startsWith(github.event.head_commit.message, '[changelog] Post-release'))
&& github.sha
)
|| github.ref
}}
cancel-in-progress: true
jobs:
detect-squash-merge:
name: 🧯 No squash on release
# Detect squash merges on release PRs and open an issue instead of releasing.
# See RELEASE_COMMIT_PATTERN in repomatic/metadata.py for the detection mechanism.
if: >-
github.event_name == 'push'
&& startsWith(github.event.head_commit.message, 'Release `v')
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@v6.0.2
- uses: astral-sh/setup-uv@v7.3.1
- name: Extract PR reference
id: extract
env:
COMMIT_MSG: ${{ github.event.head_commit.message }}
run: |
pr_ref=$(echo "${COMMIT_MSG}" | grep -oE '#[0-9]+' | tail -1)
echo "pr_ref=${pr_ref}" >> "$GITHUB_OUTPUT"
- name: Generate issue body
id: issue-metadata
env:
PR_REF: ${{ steps.extract.outputs.pr_ref }}
run: >
uvx --no-progress --from . repomatic pr-body
--template detect-squash-merge
--pr-ref "${PR_REF}"
--output "$GITHUB_OUTPUT"
- name: Open issue to notify maintainer
id: issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TITLE: ${{ steps.issue-metadata.outputs.title }}
BODY: ${{ steps.issue-metadata.outputs.body }}
run: |
issue_url=$(gh issue create \
--title "${TITLE}" \
--assignee "${GITHUB_ACTOR}" \
--body "${BODY}")
echo "issue_url=${issue_url}" >> "$GITHUB_OUTPUT"
- name: Fail workflow
env:
ISSUE_URL: ${{ steps.issue.outputs.issue_url }}
run: |
echo "::error::Squash merge detected. Release was skipped. See ${ISSUE_URL}"
exit 1
metadata:
name: 🧬 Project metadata
runs-on: ubuntu-slim
outputs:
metadata: ${{ steps.metadata.outputs.metadata }}
steps:
- uses: actions/checkout@v6.0.2
with:
# Checkout pull request HEAD commit to ignore actions/checkout's merge commit. Fallback to push SHA.
# See: https://github.com/actions/checkout/issues/504
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# We're going to browse all new commits.
fetch-depth: 0
- name: List all branches
run: git branch --all
- name: List all commits
run: git log --decorate=full --oneline
- uses: astral-sh/setup-uv@v7.3.1
- name: Run repomatic metadata
id: metadata
run: >
uvx --no-progress --from . repomatic metadata
--format github-json --output "$GITHUB_OUTPUT"
new_commits_matrix release_commits_matrix build_targets nuitka_matrix
is_python_project package_name current_version released_version
release_notes release_notes_with_admonition skip_binary_build
build-package:
name: πŸ“¦ Build package (${{ matrix.short_sha }})
needs:
- metadata
if: fromJSON(needs.metadata.outputs.metadata).is_python_project
strategy:
matrix: >-
${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix
|| fromJSON(needs.metadata.outputs.metadata).new_commits_matrix }}
runs-on: ubuntu-slim
permissions:
id-token: write
attestations: write
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ matrix.commit }}
- uses: astral-sh/setup-uv@v7.3.1
- name: Build package
run: uv --no-progress build
- name: Generate build attestations
uses: actions/attest-build-provenance@v3.2.0
with:
subject-path: ./dist/*
- name: Upload artifacts
uses: actions/upload-artifact@v6.0.0
with:
name: ${{ github.event.repository.name }}-${{ matrix.short_sha }}
path: ./dist/*
compile-binaries:
name: "${{ matrix.state == 'stable' && 'βœ…' || '⁉️' }} ${{ matrix.os }}, ${{ matrix.short_sha }} build"
needs:
- metadata
- create-release
# Skip binary compilation for branches that don't affect code (e.g., update-mailmap, format-markdown).
# Use always() because create-release is skipped on non-release pushes and PRs.
if: >-
always()
&& needs.metadata.result == 'success'
&& fromJSON(needs.metadata.outputs.metadata).nuitka_matrix
&& !(fromJSON(needs.metadata.outputs.metadata).skip_binary_build || false)
strategy:
matrix: ${{ fromJSON(needs.metadata.outputs.metadata).nuitka_matrix }}
runs-on: ${{ matrix.os }}
# We keep going when a job flagged as not stable fails.
continue-on-error: ${{ matrix.state == 'unstable' }}
permissions:
id-token: write
attestations: write
contents: write
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ matrix.commit }}
- uses: astral-sh/setup-uv@v7.3.1
- name: Setup venv
run: uv --no-progress venv --python 3.13
- name: Install Nuitka
run: uv --no-progress pip install 'nuitka[onefile]==2.8.10'
- name: Pre-bake version with commit hash
if: contains(matrix.current_version, '.dev')
env:
SHORT_SHA: ${{ matrix.short_sha }}
run: >
uvx --no-progress --from . repomatic
prebake-version --hash "${SHORT_SHA}"
- name: Nuitka + compilers versions
run: uv --no-progress run --frozen -- nuitka --version
- name: Build binary
id: build-binary
continue-on-error: true
# Project-specific Nuitka flags come from [tool.repomatic]
# nuitka-extra-args in pyproject.toml, passed via the build matrix.
env:
NUITKA_EXTRA_ARGS: ${{ matrix.nuitka_extra_args }}
BIN_NAME: ${{ matrix.bin_name }}
MODULE_PATH: ${{ matrix.module_path }}
shell: bash
run: |
# shellcheck disable=SC2086
uv --no-progress run --frozen -- nuitka \
--onefile --assume-yes-for-downloads \
${NUITKA_EXTRA_ARGS} \
--output-filename="${BIN_NAME}" "${MODULE_PATH}"
- name: Upload Nuitka crash report
uses: actions/upload-artifact@v6.0.0
with:
name: nuitka-crash-report-${{ matrix.os }}-${{ matrix.short_sha }}.xml
if-no-files-found: ignore
path: nuitka-crash-report.xml
- if: steps.build-binary.outcome == 'failure'
run: |
echo "Nuitka build failed, skipping the rest of the steps."
exit 1
- name: Install exiftool - Linux
if: runner.os == 'Linux'
run: sudo apt --quiet --yes install exiftool
- name: Install exiftool - macOS
if: runner.os == 'macOS'
run: brew install exiftool
- name: Install exiftool - Windows
if: runner.os == 'Windows'
run: choco install exiftool --no-progress --yes --retry-count=3
- name: Verify binary architecture
env:
TARGET: ${{ matrix.target }}
BIN_NAME: ${{ matrix.bin_name }}
run: >
uvx --no-progress --from . repomatic
verify-binary --target "${TARGET}" --binary "${BIN_NAME}"
- name: Upload binaries
uses: actions/upload-artifact@v6.0.0
with:
# Artifact name includes SHA for uniqueness across commits; the file
# inside uses the clean release name (no SHA suffix).
name: ${{ matrix.bin_name }}-${{ matrix.short_sha }}
if-no-files-found: warn
path: ${{ matrix.bin_name }}
- name: Generate binary attestation
if: >-
github.ref == 'refs/heads/main'
&& fromJSON(needs.metadata.outputs.metadata).release_commits_matrix
uses: actions/attest-build-provenance@v3.2.0
with:
subject-path: ${{ matrix.bin_name }}
- name: Upload binary to GitHub release
if: >-
github.ref == 'refs/heads/main'
&& fromJSON(needs.metadata.outputs.metadata).release_commits_matrix
env:
GH_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }}
CURRENT_VERSION: ${{ matrix.current_version }}
BIN_NAME: ${{ matrix.bin_name }}
run: >
gh release upload "v${CURRENT_VERSION}"
"${BIN_NAME}"
--repo "${{ github.repository }}"
test-binaries:
name: "${{ matrix.state == 'stable' && 'βœ…' || '⁉️' }} ${{ matrix.os }}, ${{ matrix.short_sha }} test"
needs:
- metadata
- compile-binaries
# Skip binary tests for branches that don't affect code (e.g., update-mailmap, format-markdown).
if: >
fromJSON(needs.metadata.outputs.metadata).nuitka_matrix
&& !(fromJSON(needs.metadata.outputs.metadata).skip_binary_build || false)
strategy:
matrix: ${{ fromJSON(needs.metadata.outputs.metadata).nuitka_matrix }}
runs-on: ${{ matrix.os }}
# We keep going when a job flagged as not stable fails.
continue-on-error: ${{ matrix.state == 'unstable' }}
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ matrix.commit }}
- name: Download artifact
uses: actions/download-artifact@v7.0.0
id: artifacts
with:
name: ${{ matrix.bin_name }}-${{ matrix.short_sha }}
- name: Set binary permissions
if: runner.os != 'Windows'
env:
DOWNLOAD_PATH: ${{ steps.artifacts.outputs.download-path }}
BIN_NAME: ${{ matrix.bin_name }}
run: chmod +x "${DOWNLOAD_PATH}/${BIN_NAME}"
- uses: astral-sh/setup-uv@v7.3.1
- name: Run test plan for binary
env:
DOWNLOAD_PATH: ${{ steps.artifacts.outputs.download-path }}
BIN_NAME: ${{ matrix.bin_name }}
run: >
uvx --no-progress --from . repomatic test-plan
--binary "${DOWNLOAD_PATH}/${BIN_NAME}"
create-tag:
name: πŸ“Œ Tag release (${{ matrix.short_sha }})
needs:
- metadata
# Only consider pushes to main branch as triggers for releases.
if: github.ref == 'refs/heads/main' && fromJSON(needs.metadata.outputs.metadata).release_commits_matrix
strategy:
matrix: ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }}
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ matrix.commit }}
# PAT required so tag pushes trigger downstream on.push.tags workflows.
# See repomatic/git_ops.py module docstring.
token: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }}
- uses: astral-sh/setup-uv@v7.3.1
- name: Create and push tag
# Idempotent: skips if tag already exists instead of failing. This allows re-running
# workflows interrupted after tag creation.
env:
CURRENT_VERSION: ${{ matrix.current_version }}
COMMIT: ${{ matrix.commit }}
run: >
uvx --no-progress --from . repomatic git-tag
--tag "v${CURRENT_VERSION}"
--commit "${COMMIT}"
--skip-existing
publish-pypi:
name: 🐍 Publish to PyPI (${{ matrix.short_sha }})
needs:
- metadata
- build-package
- create-tag
if: fromJSON(needs.metadata.outputs.metadata).package_name
strategy:
matrix: ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }}
runs-on: ubuntu-slim
permissions:
# Allow GitHub's OIDC provider to create a JSON Web Token:
# https://github.blog/changelog/2023-06-15-github-actions-securing-openid-connect-oidc-token-permissions-in-reusable-workflows/
# https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings
id-token: write
attestations: write
steps:
- uses: astral-sh/setup-uv@v7.3.1
- name: Download build artifacts
uses: actions/download-artifact@v7.0.0
id: download
with:
name: ${{ github.event.repository.name }}-${{ matrix.short_sha }}
- name: Generate attestations
uses: actions/attest-build-provenance@v3.2.0
with:
subject-path: "${{ steps.download.outputs.download-path }}/*"
- name: Push to PyPI
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
DOWNLOAD_PATH: ${{ steps.download.outputs.download-path }}
run: >
uv --no-progress publish --token "${PYPI_TOKEN}"
"${DOWNLOAD_PATH}/*"
create-release:
name: πŸ™ Create GitHub release draft (${{ matrix.short_sha }})
needs:
- metadata
- build-package
- create-tag
- publish-pypi
# Make sure this job always starts if create-tag ran and succeeded.
if: always() && needs.create-tag.result == 'success'
strategy:
matrix: ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }}
runs-on: ubuntu-slim
permissions:
# Allow GitHub's OIDC provider to create a JSON Web Token:
# https://github.blog/changelog/2023-06-15-github-actions-securing-openid-connect-oidc-token-permissions-in-reusable-workflows/
# https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings
id-token: write
attestations: write
# Allow project without WORKFLOW_UPDATE_GITHUB_PAT to create a GitHub release.
contents: write
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ matrix.commit }}
- uses: astral-sh/setup-uv@v7.3.1
with:
enable-cache: false
- name: Download Python package
# Do not fetch build artifacts if build-package was skipped (non-Python projects).
if: needs.build-package.result != 'skipped'
uses: actions/download-artifact@v7.0.0
id: artifacts
with:
path: release_artifact
name: ${{ github.event.repository.name }}-${{ matrix.short_sha }}
- name: Generate attestations
# Do not try to attest artifacts if none have been downloaded.
if: steps.artifacts.outputs.download-path
uses: actions/attest-build-provenance@v3.2.0
with:
subject-path: release_artifact/*
- name: Delete dev pre-release
# Clean up all rolling dev releases before creating the real release.
# See repomatic/github/dev_release.py for rationale.
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }}
run: uvx --no-progress --from . repomatic sync-dev-release --live --delete
- name: Create GitHub release draft
# Idempotent: skips if release already exists (e.g. workflow re-run).
env:
GH_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }}
CURRENT_VERSION: ${{ matrix.current_version }}
COMMIT: ${{ matrix.commit }}
REPO: ${{ github.repository }}
RELEASE_NOTES: >-
${{ needs.publish-pypi.result == 'success'
&& fromJSON(needs.metadata.outputs.metadata).release_notes_with_admonition
|| fromJSON(needs.metadata.outputs.metadata).release_notes }}
run: |
tag="v${CURRENT_VERSION}"
if gh release view "${tag}" --repo "${REPO}" > /dev/null 2>&1; then
echo "Release ${tag} already exists, skipping creation."
else
shopt -s nullglob
files=(release_artifact/*)
gh release create "${tag}" \
--draft \
--target "${COMMIT}" \
--title "${tag}" \
--repo "${REPO}" \
--notes-file - \
"${files[@]}" <<< "${RELEASE_NOTES}"
fi
publish-release:
name: πŸŽ‰ Publish GitHub release (${{ matrix.short_sha }})
needs:
- metadata
- create-release
- compile-binaries
# Wait for all upstream jobs regardless of result, but only publish if the draft was created.
if: >-
always()
&& needs.create-release.result == 'success'
strategy:
matrix: ${{ fromJSON(needs.metadata.outputs.metadata).release_commits_matrix }}
runs-on: ubuntu-slim
permissions:
contents: write
steps:
- name: Publish release
env:
GH_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }}
CURRENT_VERSION: ${{ matrix.current_version }}
REPO: ${{ github.repository }}
run: >
gh release edit "v${CURRENT_VERSION}"
--draft=false
--repo "${REPO}"
sync-dev-release:
name: πŸ”„ Sync dev pre-release
needs:
- metadata
- build-package
- compile-binaries
# Run on non-release pushes to main after builds complete.
# Uses always() because compile-binaries depends on create-release (skipped for non-release pushes).
if: >-
always()
&& github.ref == 'refs/heads/main'
&& !fromJSON(needs.metadata.outputs.metadata).release_commits_matrix
&& fromJSON(needs.metadata.outputs.metadata).current_version
runs-on: ubuntu-slim
permissions:
contents: write
steps:
- uses: actions/checkout@v6.0.2
- uses: astral-sh/setup-uv@v7.3.1
- name: Download all build artifacts
uses: actions/download-artifact@v7.0.0
with:
path: release_assets
merge-multiple: true
- name: Sync dev release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >
uvx --no-progress --from . repomatic sync-dev-release --live
--upload-assets release_assets