Skip to content
Merged
142 changes: 142 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: Release

# Tag-triggered release.
#
# How to cut a release:
#
# git tag v0.3.3 # pick the next version; must start with "v"
# git push origin v0.3.3
#
# Pushing a tag that looks like vX.Y.Z starts this workflow. It builds the
# project in Release, packages everything into a .zip with CPack, and publishes
# a GitHub Release named after the tag with the .zip attached.
#
# The build version comes from this same tag: the top-level CMakeLists.txt runs
# `git describe --tags --match "v*"` and extracts the numeric X.Y.Z. The release
# is always named after the full tag, but note that any pre-release suffix
# (e.g. the `-rc1` in v0.3.3-rc1) is NOT carried into the built PROJECT_VERSION,
# which would be 0.3.3 in that example. There are no version files to keep in
# sync.

on:
push:
tags:
# GitHub tag filters use glob (minimatch) syntax, not regex, so we cannot
# use `+` quantifiers. Requiring the two dots enforces the vX.Y.Z shape and
# avoids firing on tags like v1 or v12foo. The trailing segment's `*` still
# allows pre-release suffixes such as v0.3.3-rc1. (This does not reject
# v1.2.3.4; add a runtime semver check if that strictness is needed.)
- "v[0-9]*.[0-9]*.[0-9]*"

permissions:
contents: write # needed to create the GitHub Release

env:
# Pin the architecture/triplet everywhere so vcpkg, CMake, and the packaged
# artifact all agree on x64 and we never accidentally publish a 32-bit build.
VCPKG_DEFAULT_TRIPLET: x64-windows
VCPKG_TARGET_TRIPLET: x64-windows

jobs:
release:
runs-on: windows-latest
steps:
- name: Validate tag is strict semver
# The on.push.tags glob can only require the vX.Y.Z *shape*; it can't
# enforce digits-only segments, so tags like v1foo.2.3 or v1.2.3.4 would
# still start this job. Reject anything that isn't strict
# vMAJOR.MINOR.PATCH (with an optional -prerelease / +build suffix) so we
# never publish an unintended release.
shell: bash
run: |
tag="${{ github.ref_name }}"
# This is the official semver.org recommended regex, adapted to POSIX
# ERE (bash [[ =~ ]]): non-capturing groups become plain groups and \d
# becomes [0-9]. Enforcing the full grammar means:
# * MAJOR/MINOR/PATCH are numeric with no leading zeroes (0 | [1-9]...)
# * each dot-separated pre-release/build identifier is non-empty and
# well-formed, so junk like v1.2.3-alpha..1 or v1.2.3+build..5 (empty
# identifiers) is rejected even though it "looks" close to semver.
# * a pre-release numeric identifier has no leading zeroes, but build
# identifiers may (per spec).
semver='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$'
if [[ ! "$tag" =~ $semver ]]; then
echo "::error::Tag '$tag' is not a strict vX.Y.Z[-prerelease][+build] semver tag; refusing to release."
exit 1
fi
echo "Tag '$tag' accepted."

- uses: actions/checkout@v5
with:
submodules: 'recursive'
# Full history + tags so the CMake `git describe` version resolves to
# the tag that triggered this run.
fetch-depth: 0
fetch-tags: true

- name: Set reusable strings
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
echo "dist-dir=${{ github.workspace }}/dist" >> "$GITHUB_OUTPUT"
Comment thread
EmJayGee marked this conversation as resolved.

- name: Bootstrap vcpkg
shell: pwsh
run: ${{ github.workspace }}/vcpkg/bootstrap-vcpkg.bat

- name: Run vcpkg
shell: pwsh
run: ${{ github.workspace }}/vcpkg/vcpkg.exe install --triplet x64-windows

- name: Configure CMake
# The Visual Studio generator selected by -A x64 is multi-config, so the
# build type is chosen later via --config / --build-config / -C Release
# rather than CMAKE_BUILD_TYPE. Only BUILD_TESTING controls test builds
# in this project's CMake.
run: >
cmake
-A x64
-D CMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake
-D VCPKG_TARGET_TRIPLET=x64-windows
-D BUILD_TESTING=ON
-B ${{ steps.strings.outputs.build-output-dir }}
Comment on lines +99 to +103
-S ${{ github.workspace }}

- name: Build
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release --parallel

- name: Test
# Don't publish a release artifact that fails the suite the rest of CI runs.
working-directory: ${{ steps.strings.outputs.build-output-dir }}
run: ctest --build-config Release --output-on-failure

- name: Package (CPack -> .zip)
run: >
cpack
--config ${{ steps.strings.outputs.build-output-dir }}/CPackConfig.cmake
-C Release
-B ${{ steps.strings.outputs.dist-dir }}

- name: Publish GitHub Release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# github.ref_name is the tag that triggered this run, e.g. v0.3.3.
# A SemVer pre-release is a '-' that comes before any '+build' metadata
# (e.g. v0.3.3-rc1). Build metadata may itself contain hyphens
# (e.g. v1.2.3+build-foo), so strip everything from the first '+'
# before testing for a pre-release hyphen to avoid false positives.
tag="${{ github.ref_name }}"
version_no_build="${tag%%+*}"
PRERELEASE_FLAG=""
case "$version_no_build" in
*-*) PRERELEASE_FLAG="--prerelease" ;;
esac

gh release create "${{ github.ref_name }}" \
"${{ steps.strings.outputs.dist-dir }}"/*.zip \
--title "${{ github.ref_name }}" \
--generate-notes \
$PRERELEASE_FLAG
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,21 @@ install(
DESTINATION
${SYSCONFIG_INSTALL_DIR}
)

# Packaging (CPack). This generates ${CMAKE_BINARY_DIR}/CPackConfig.cmake so that
# `cpack` can build release artifacts in CI. Keep the generator list portable:
# ZIP needs no extra tooling on the runner, unlike NSIS/DEB.
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_PACKAGE_VENDOR "Microsoft")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "m C++ libraries")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}")
set(CPACK_VERBATIM_VARIABLES ON)

if(WIN32)
set(CPACK_GENERATOR "ZIP")
else()
set(CPACK_GENERATOR "TGZ")
endif()

include(CPack)
Loading