fmt/.github/workflows/release.yml
Victor Zverovich 24ab1de71c Document why the SLSA generator is referenced by tag
Scorecard flags the tag as an unpinned dependency, but the generator
doesn't support being pinned by hash.
2026-11-07 09:34:40 -08:00

146 lines
5.8 KiB
YAML

# Builds the release source package in CI for a draft release (typically
# created via support/release.py), uploads the zip to that release, and
# attaches a SLSA v1.0 provenance attestation generated by the OpenSSF
# slsa-github-generator. The maintainer reviews the draft (which by then has
# both the zip and *.intoto.jsonl attached) and clicks Publish to finalize.
#
# The source package includes the pre-rendered HTML documentation (doc-html/),
# so this workflow builds the docs and stages them into the source tree before
# running CPack's package_source target.
#
# This makes the provenance attest to the actual build that produced the
# artifact, rather than just attesting to a hash observed after the fact.
#
# GitHub does not fire `release: created` for draft releases, so release.py
# triggers this workflow explicitly via workflow_dispatch, passing the tag to
# attach to and the ref to build from. The `release: created` trigger is kept
# for non-draft releases created directly through the GitHub UI.
name: release
on:
release:
types: [created]
workflow_dispatch:
inputs:
tag_name:
description: "Release tag to attach the artifacts to (e.g. 12.2.0)"
required: true
type: string
ref:
description: "Git ref to build the source package from"
required: false
default: release
type: string
permissions: read-all
jobs:
build:
name: Build source package
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
package: ${{ steps.build.outputs.package }}
tag: ${{ steps.vars.outputs.tag }}
steps:
- name: Resolve tag and ref for both event types
id: vars
run: |
echo "tag=${{ github.event.release.tag_name || inputs.tag_name }}" >> "$GITHUB_OUTPUT"
echo "ref=${{ github.event.release.target_commitish || inputs.ref }}" >> "$GITHUB_OUTPUT"
- name: Checkout the release ref
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ steps.vars.outputs.ref }}
persist-credentials: false
- name: Install the documentation toolchain
# The source zip ships the pre-rendered HTML docs (doc-html/), so the
# docs must be built before packaging. doxygen feeds the API reference;
# mkdocs plus the pinned, hash-verified deps render the site. The venv
# lives outside the checkout so it is not swept into the source zip, and
# is added to PATH so CMake's find_program(MKDOCS) and the doxygen call
# in the mkdocstrings handler both resolve in later steps.
run: |
sudo apt-get update
sudo apt-get install -y doxygen
python3 -m venv "$RUNNER_TEMP/doc-venv"
"$RUNNER_TEMP/doc-venv/bin/pip" install --quiet --upgrade pip
"$RUNNER_TEMP/doc-venv/bin/pip" install --quiet --require-hashes \
-r support/doc-requirements.txt
echo "$RUNNER_TEMP/doc-venv/bin" >> "$GITHUB_PATH"
- name: Build source zip via CPack
id: build
# Build the docs and stage the rendered site at the top level so the
# CPack source package includes doc-html/. The build/ tree itself is
# excluded by CPACK_SOURCE_IGNORE_FILES.
run: |
cmake -B build .
cmake --build build --target doc
cp -r build/doc-html doc-html
cmake --build build --target package_source
pkg=$(ls build/fmt-*.zip)
test -f "$pkg"
echo "package=$pkg" >> "$GITHUB_OUTPUT"
- name: Compute base64-encoded SHA-256 subjects
id: hash
run: |
file="${{ steps.build.outputs.package }}"
subjects=$(cd "$(dirname "$file")" && sha256sum "$(basename "$file")")
echo "hashes=$(printf '%s' "$subjects" | base64 -w0)" >> "$GITHUB_OUTPUT"
- name: Upload zip to the release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release upload "${{ steps.vars.outputs.tag }}" \
"${{ steps.build.outputs.package }}" \
--repo "${{ github.repository }}" --clobber
# Generate the provenance only; do not let the generator upload it. With
# upload-assets, the generator creates and publishes its own release (and
# tag) to host the attestation, which duplicates our draft. Instead we
# download the attestation artifact and attach it to the draft below.
provenance:
needs: [build]
permissions:
actions: read
id-token: write
# contents: write is required because the generator's (skipped)
# upload-assets job declares it, and a reusable workflow's job
# permissions may not exceed the caller's, or the run fails at startup.
contents: write
# The generator must be referenced by an exact vX.Y.Z tag rather than
# pinned by hash, or slsa-verifier can't verify the generator's ref.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
with:
base64-subjects: ${{ needs.build.outputs.hashes }}
provenance-name: "fmt-${{ needs.build.outputs.tag }}.intoto.jsonl"
upload-assets: false
attach-provenance:
name: Attach provenance to the release
needs: [build, provenance]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download the provenance attestation
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ needs.provenance.outputs.provenance-name }}
- name: Attach provenance to the draft release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release upload "${{ needs.build.outputs.tag }}" \
"${{ needs.provenance.outputs.provenance-name }}" \
--repo "${{ github.repository }}" --clobber