Skip to content

Bump gha-utils from 4.17.2 to 4.17.3 #6159

Bump gha-utils from 4.17.2 to 4.17.3

Bump gha-utils from 4.17.2 to 4.17.3 #6159

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
inputs:
unstable-targets:
description: List of unstable targets on which the build should be attempted but not fatal
required: false
# XXX This has to be a string because workflow inputs are not allowed to be complex types.
# Still, thanks to gha-utils resilience, we can pass a list of targets as a string, separated
# by arbitrary sepatarators.
type: string
timeout:
description: Timeout in seconds for each binary test
required: false
type: number
test-plan-file:
description: YAML file containing the full test plan for binaries
required: false
type: string
default: ./tests/cli-test-plan.yaml
test-plan:
description: Test plan for binaries
required: false
# XXX This has to be a string because workflow inputs are not allowed to be complex types.
type: string
outputs:
nuitka_matrix:
description: Nuitka build matrix
value: ${{ jobs.project-metadata.outputs.nuitka_matrix }}
# Target are chosen so that all commits get a chance to have their build tested.
push:
branches:
- main
pull_request:
# Defaults sets in workflow_call.inputs or workflow_dispatch.inputs are not propagated to other events.
# We have to manually manage them: https://github.com/orgs/community/discussions/39357#discussioncomment-7500641
env:
test-plan-file: >-
${{ inputs.test-plan-file == null && './tests/cli-test-plan.yaml' || inputs.test-plan-file }}
test-plan: ${{ inputs.test-plan }}
concurrency:
# Group workflow jobs so new commits cancels in-progress execution triggered by previous commits. Source:
# https://mail.python.org/archives/list/[email protected]/thread/PCBCQMJF64JGRBOX7E2EE4YLKHT4DI55/
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
project-metadata:
name: Project metadata
runs-on: ubuntu-24.04
outputs:
# There's a design issue with GitHub actions: matrix outputs are not cumulative. The last job wins
# (see: https://github.community/t/bug-jobs-output-should-return-a-list-for-a-matrix-job/128626).
# This means in a graph of jobs, a matrix-based one is terminal, and cannot be depended on. Same goes for
# (reusable) workflows. We use this preliminary job to produce all matrix we need to trigger depending jobs
# over the dimensions.
new_commits_matrix: ${{ steps.project-metadata.outputs.new_commits_matrix }}
release_commits_matrix: ${{ steps.project-metadata.outputs.release_commits_matrix }}
# Export Python project metadata.
nuitka_matrix: ${{ steps.project-metadata.outputs.nuitka_matrix }}
is_python_project: ${{ steps.project-metadata.outputs.is_python_project }}
package_name: ${{ steps.project-metadata.outputs.package_name }}
release_notes: ${{ steps.project-metadata.outputs.release_notes }}
steps:
- uses: actions/[email protected]
with:
# Checkout pull request HEAD commit to ignore actions/checkout's merge commit. Fallback to push SHA.
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
- name: Install uv
run: |
python -m pip install -r https://raw.githubusercontent.com/kdeldycke/workflows/main/requirements/uv.txt
- name: Run gha-utils metadata
id: project-metadata
env:
GITHUB_CONTEXT: ${{ toJSON(github) }}
run: >
uvx
--with-requirements https://raw.githubusercontent.com/kdeldycke/workflows/main/requirements/gha-utils.txt
--
gha-utils --verbosity DEBUG metadata
${{ inputs.unstable-targets != null && format('--unstable-targets "{0}"', inputs.unstable-targets) || '' }}
--overwrite "$GITHUB_OUTPUT"
package-build:
name: Build package
needs:
- project-metadata
if: fromJSON(needs.project-metadata.outputs.is_python_project)
strategy:
matrix: ${{ fromJSON(needs.project-metadata.outputs.new_commits_matrix) }}
runs-on: ubuntu-24.04
steps:
- uses: actions/[email protected]
with:
ref: ${{ matrix.commit }}
- name: Install uv
run: |
python -m pip install -r https://raw.githubusercontent.com/kdeldycke/workflows/main/requirements/uv.txt
- name: Build package
run: |
uv --no-progress build
- name: Upload artifacts
uses: actions/[email protected]
with:
name: ${{ github.event.repository.name }}-${{ matrix.short_sha }}
path: ./dist/*
# TODO: Should we also attest the archive created here? See:
# https://github.com/actions/attest-build-provenance?tab=readme-ov-file#integration-with-actionsupload-artifact
compile-binaries:
name: "${{ matrix.state == 'stable' && '✅' || '⁉️' }} Build on ${{ matrix.os }}, ${{ matrix.short_sha }}"
needs:
- project-metadata
if: needs.project-metadata.outputs.nuitka_matrix
strategy:
matrix: ${{ fromJSON(needs.project-metadata.outputs.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/[email protected]
with:
ref: ${{ matrix.commit }}
# XXX We use setup-python to install ARM64 flavor of Python on Windows because uv does not support it yet:
# https://github.com/astral-sh/python-build-standalone/issues/386
# https://github.com/Nuitka/Nuitka/issues/3449#issuecomment-2889794114
- uses: actions/[email protected]
if: matrix.os == 'windows-11-arm'
with:
# XXX Python version here does not really matter but we had to set it for the action to pick-up the
# architecture parameter.
python-version: "3.13.3"
architecture: arm64
- name: Install uv
run: |
python -m pip install -r https://raw.githubusercontent.com/kdeldycke/workflows/main/requirements/uv.txt
- name: Build binary
# XXX Python for Windows ARM64 has not been shipped yet, so don't make uv try to fall back on x64 Python by
# forcing the --no-managed-python option. See:
# https://github.com/astral-sh/python-build-standalone/pull/387#issuecomment-2836029040
# https://github.com/astral-sh/uv/issues/12906
# https://github.com/astral-sh/uv/pull/13719
# https://github.com/astral-sh/uv/pull/13724
run: >
uv run
${{ matrix.os == 'windows-11-arm' && '--no-managed-python' || '' }}
--with-requirements https://raw.githubusercontent.com/kdeldycke/workflows/main/requirements/nuitka.txt
--
nuitka --onefile --assume-yes-for-downloads --output-filename=${{ matrix.bin_name }} ${{ matrix.module_path }}
- 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
- name: Binary metadata
# Ubuntu:
# CPU Type : Arm 64-bits (Armv8/AArch64)
# CPU Type : AMD x86-64
# macOS:
# CPU Type : ARM 64-bit
# CPU Type : x86 64-bit
# Windows
# Machine Type : ARM64 little endian
# Machine Type : AMD AMD64
run: |
exiftool${{ runner.os == 'Windows' && '.exe' || '' }} ${{ matrix.bin_name }}
- name: Upload binaries
uses: actions/[email protected]
with:
name: ${{ matrix.bin_name }}
if-no-files-found: warn
path: ${{ matrix.bin_name }}
test-binaries:
name: "${{ matrix.state == 'stable' && '✅' || '⁉️' }} Test on ${{ matrix.os }}, ${{ matrix.short_sha }}"
needs:
- project-metadata
- compile-binaries
if: needs.project-metadata.outputs.nuitka_matrix
strategy:
matrix: ${{ fromJSON(needs.project-metadata.outputs.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/[email protected]
with:
ref: ${{ matrix.commit }}
- name: Download artifact
uses: actions/[email protected]
id: artifacts
with:
name: ${{ matrix.bin_name }}
- name: Set binary permissions
if: runner.os != 'Windows'
run: |
chmod +x ${{ steps.artifacts.outputs.download-path }}/${{ matrix.bin_name }}
- name: Install uv
run: |
python -m pip install -r https://raw.githubusercontent.com/kdeldycke/workflows/main/requirements/uv.txt
- name: Run test plan for binary
run: >
uvx
--with-requirements https://raw.githubusercontent.com/kdeldycke/workflows/main/requirements/gha-utils.txt
--
gha-utils test-plan
${{ inputs.timeout != null && format('--timeout {0}', inputs.timeout) || '' }}
--binary "${{ steps.artifacts.outputs.download-path }}/${{ matrix.bin_name }}"
--plan-file "${{ env.test-plan-file }}"
${{ env.test-plan != null && '--plan-envvar test-plan' || '' }}
git-tag:
name: Tag release
needs:
- project-metadata
# Only consider pushes to main branch as triggers for releases.
if: github.ref == 'refs/heads/main' && needs.project-metadata.outputs.release_commits_matrix
strategy:
matrix: ${{ fromJSON(needs.project-metadata.outputs.release_commits_matrix) }}
runs-on: ubuntu-24.04
steps:
- uses: actions/[email protected]
with:
ref: ${{ matrix.commit }}
# XXX We need custom PAT with workflows permissions because tag generation will work but it will not trigger
# any other workflows that use `on.push.tags` triggers. See:
# https://stackoverflow.com/questions/60963759/use-github-actions-to-create-a-tag-but-not-a-release#comment135891921_64479344
# https://github.com/orgs/community/discussions/27028
token: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }}
- name: Check if tag exists
id: tag_exists
run: |
echo "tag_exists=$(git show-ref --tags "v${{ matrix.current_version }}" --quiet )" | tee -a "$GITHUB_OUTPUT"
- name: Push tag
# Skip the tag creation if it already exists instead of failing flat. This allows us to re-run the workflow if
# it was interrupted the first time. Which is really useful if the tagging fails during a release: we can
# simply push the new tag by hand and re-launch the workflow run.
if: ${{ ! steps.tag_exists.outputs.tag_exists }}
run: |
git tag "v${{ matrix.current_version }}" "${{ matrix.commit }}"
git push origin "v${{ matrix.current_version }}"
pypi-publish:
name: Publish to PyPi
needs:
- project-metadata
- package-build
- git-tag
if: needs.project-metadata.outputs.package_name
strategy:
matrix: ${{ fromJSON(needs.project-metadata.outputs.release_commits_matrix) }}
runs-on: ubuntu-24.04
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:
- name: Install uv
run: |
python -m pip install -r https://raw.githubusercontent.com/kdeldycke/workflows/main/requirements/uv.txt
- name: Download build artifacts
uses: actions/[email protected]
id: download
with:
name: ${{ github.event.repository.name }}-${{ matrix.short_sha }}
- name: Generate attestations
uses: actions/[email protected]
with:
subject-path: "${{ steps.download.outputs.download-path }}/*"
- name: Push to PyPi
run: |
uv --no-progress publish --token "${{ secrets.PYPI_TOKEN }}" "${{ steps.download.outputs.download-path }}/*"
github-release:
name: Publish GitHub release
needs:
- project-metadata
- package-build
- compile-binaries
- git-tag
# Make sure this job always starts if git-tag ran and succeeded.
if: always() && needs.git-tag.result == 'success'
strategy:
matrix: ${{ fromJSON(needs.project-metadata.outputs.release_commits_matrix) }}
runs-on: ubuntu-24.04
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:
- name: Download all artifacts
# Do not fetch build artifacts if all jobs producing them were skipped.
if: needs.package-build.result != 'skipped' || needs.compile-binaries.result != 'skipped'
uses: actions/[email protected]
id: artifacts
with:
path: release_artifact
# Only consider artifacts produced by the release commit.
pattern: "*-${{ matrix.short_sha }}*"
merge-multiple: true
- name: Collect all artefacts, rename binaries
# Do not try to rename artifacts if none have been downloaded.
if: steps.artifacts.outputs.download-path
id: collect_artifacts
shell: python
run: |
import json
import os
from pathlib import Path
from random import randint
download_folder = Path("""${{ steps.artifacts.outputs.download-path }}""")
nuitka_matrix_json = """${{ needs.project-metadata.outputs.nuitka_matrix }}"""
binaries = {}
if nuitka_matrix_json:
nuitka_matrix = json.loads(nuitka_matrix_json)
binaries = {entry["bin_name"] for entry in nuitka_matrix["include"] if "bin_name" in entry}
artifacts_path = []
for artifact in download_folder.glob("*"):
print(f"Processing {artifact} ...")
assert artifact.is_file()
# Rename binary artifacts to remove the build ID.
if artifact.name in binaries:
new_name = f'{artifact.stem.split("""-${{ matrix.short_sha }}""", 1)[0]}{artifact.suffix}'
new_path = artifact.with_name(new_name)
print(f"Renaming {artifact} to {new_path} ...")
assert not new_path.exists()
artifact.rename(new_path)
artifacts_path.append(new_path)
# Collect other artifacts as-is.
else:
print(f"Collecting {artifact} ...")
artifacts_path.append(artifact)
# Produce a unique delimiter to feed multiline content to GITHUB_OUTPUT:
# https://github.com/orgs/community/discussions/26288#discussioncomment-3876281
delimiter = f"ghadelimiter_{randint(10**8, (10**9) - 1)}"
output = f"artifacts_path<<{delimiter}\n"
output += "\n".join(str(p) for p in artifacts_path)
output += f"\n{delimiter}"
env_file = Path(os.getenv("GITHUB_OUTPUT"))
env_file.write_text(output)
- name: Generate attestations
# Do not try to attest artifacts if none have been produced.
if: steps.collect_artifacts.outputs.artifacts_path
uses: actions/[email protected]
with:
subject-path: ${{ steps.collect_artifacts.outputs.artifacts_path }}
- name: Create GitHub release
uses: softprops/[email protected]
# XXX We need custom PAT with workflows permissions because tag generation will work but it will not trigger
# any other workflows that use `on.push.tags` triggers. See:
# https://stackoverflow.com/questions/60963759/use-github-actions-to-create-a-tag-but-not-a-release#comment135891921_64479344
# https://github.com/orgs/community/discussions/27028
env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ matrix.current_version }}
target_commitish: ${{ matrix.commit }}
files: ${{ steps.collect_artifacts.outputs.artifacts_path }}
body: ${{ needs.project-metadata.outputs.release_notes }}