0015 Release workflow: one reusable workflow, CPack on every platform, called by Release Please¶
- Status: accepted
- Date: 2026-09-23
Context and problem statement¶
ADR 0007 hands releases to Release Please: merging its pull request creates the tag vX.Y.Z
and the GitHub release. ADR 0005 requires unsigned but launchable packages for Windows,
macOS and Linux with SHA-256 checksums. Something has to build those packages for every
release, attach them to it, and be testable before a release happens. How is that workflow
laid out and triggered?
Decision drivers¶
- A release made by Release Please must get its packages without a manual step.
- Tags and releases created with the repository's
GITHUB_TOKEN, which Release Please uses, do not start other workflows; a workflow listening forpush: tagsorrelease: publishedwould never run for them. - The packaging must be exercised before a real release: on a branch that changes it, and on a release candidate that produces a real pre-release.
- Package contents should come from one definition that developers can run locally, not from copy commands scattered through YAML.
- Every package is checked on the runner that built it before anything is published.
- Zero cost: GitHub-hosted runners only, which are free for public repositories.
Considered options¶
- A release workflow triggered by
push: tags, with a personal access token given to Release Please so that its tags start workflows. - Package jobs appended to the Release Please workflow.
- A separate
release.ymlwith four triggers:workflow_callfrom the Release Please workflow,pushof av*tag,workflow_dispatchfor an existing tag, andpushto a branch that touches packaging files as a dry run.
Decision outcome¶
Option 3.
- Triggers.
release-please.ymlcallsrelease.ymlwith the new tag whenrelease_createdis true. Release candidates are tagged by hand (v1.0.0-rc.1); the tag push runs the same workflow, which creates the release as a pre-release. A manual dispatch rebuilds and re-uploads the packages of an existing tag. A push to any branch other thanmainthat changes the workflow,packaging/,cmake/or aCMakeLists.txtbuilds every package with the version<project version>-dryrun.<commit>and keeps them as a workflow artifact without publishing anything. - Layout. A
preparejob derives the tag, the package version and whether to publish, and refuses a final tag that does not match the version inCMakeLists.txt. One job per platform and architecture builds thereleasepreset (release-windowson Windows) from a full clone of the tag, so thatgit describenames the tag, then runscpackand checks its output: the Windows portable ZIP and a silent install of the NSIS installer, the mounted macOS disk image (ad-hoc signature, architecture, minimum macOS version), the AppImage (command-line tool,nmeasimlink, a headless start of the desktop application) and the Flatpak bundle, installed and run. Apublishjob collects everything, writesSHA256SUMS.txtand attaches it all to the release, creating the release for a tag pushed by hand. - Runners.
windows-2022;macos-15for arm64 andmacos-15-intelfor x86_64, since a separate native build per architecture avoids building the vcpkg dependencies twice for a universal binary and lets each package be run on its own architecture;ubuntu-24.04andubuntu-24.04-armfor the x86_64 and aarch64 AppImages; the Flathub KDE builder container for the Flatpak. - Packaging definition.
cmake --installandcpackdo the work on every platform (cmake/Packaging.cmake): windeployqt and macdeployqt run from Qt's deploy script at install time, the macOS bundle is signed ad hoc at install time, CPack's NSIS, ZIP and DragNDrop generators build the Windows and macOS packages, and a CPack External generator script runs linuxdeploy for the AppImage. The workflow only provides tools and checks results. - Pinned tools. Qt, linuxdeploy, its Qt plugin and the Flatpak runtime are pinned to exact versions in the workflow and the manifest.
Option 1 was rejected because a long-lived personal token is a secret to rotate and a security liability for a single-maintainer project. Option 2 was rejected because it cannot be run for a hand-made release candidate or as a dry run without a release.
Consequences¶
- A release is complete only when the
packagesjob of the Release Please run has finished; the release checklist in the release process says how to confirm it. - Changes to packaging are tested by pushing a branch; the dry-run artifact can be downloaded and tried on real machines before merging.
- A release candidate leaves a pre-release and a tag behind that Release Please could take for the latest version; both are deleted once the candidate has been checked.
- The macOS packages need macOS 13.3 or later (the deployment target that
std::formatrequires in Apple's C++ library).
More information¶
- Release process
- ADR 0016 for the choice of Linux formats.