diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index f5eb806..31871e7 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -943,11 +943,15 @@ jobs: retention-days: 30 # --------------------------------------------------------------------------- - # Release: only runs on a v*.*.* tag push. Pulls both matrix artifacts - # down from the `build` job and attaches them to a GitHub Release. + # Release: only runs on a v*.*.* tag push. That tag is created by + # release-please (see .github/workflows/release-please.yml) when its + # release PR is merged — release-please also creates the GitHub Release + # with the generated changelog as the body. This job pulls both matrix + # artifacts down from the `build` job and ATTACHES them to that existing + # Release (it does not create one — see the attach step below). # --------------------------------------------------------------------------- release: - name: Create GitHub Release + name: Attach assets to release needs: build runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') @@ -978,14 +982,29 @@ jobs: done ls -la ../release/ - - name: Create Release + # release-please (.github/workflows/release-please.yml) already created + # this tag's GitHub Release with the auto-generated changelog as the + # body. This step does NOT create a release — softprops finds the + # existing one (matched by the tag) and (a) uploads the built + signed + # assets and (b) APPENDS the static install instructions below the + # changelog. `append_body: true` is what preserves release-please's + # changelog instead of overwriting it; we deliberately drop + # `generate_release_notes` and `name:` so we don't fight release-please + # for ownership of those fields. + # + # Caveat: append_body is NOT idempotent — it re-appends the install + # block every time this job's attach step runs again for the same tag + # (e.g. the Actions "Re-run jobs" button on an already-green release). + # A failed signing/build re-run never reaches this step, so that common + # path is safe; if a duplicate does land, trim it from the release body + # by hand. + - name: Attach signed assets to release uses: softprops/action-gh-release@v2 with: files: release/* - generate_release_notes: true + append_body: true draft: false prerelease: ${{ contains(github.ref_name, '-') }} - name: ${{ github.ref_name }} body: | ## ${{ env.LAYER_NAME }} ${{ github.ref_name }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..87ca614 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,52 @@ +name: release-please + +# Conventional-Commits release automation. +# +# On every push to main, release-please opens (and keeps updating) a single +# "release PR" that bumps the version in version.txt and regenerates +# CHANGELOG.md from the feat:/fix: commits landed since the last release. +# Merging that PR is the release trigger: release-please creates the +# vX.Y.Z git tag and a GitHub Release whose body is the generated changelog. +# +# That tag is what starts build-and-release.yml (build + Certum signing + +# asset upload). Its `release` job then attaches the signed DLL/installer to +# the very Release this action created — see the "Attach signed assets to +# release" step over there. +# +# WHY THE CUSTOM TOKEN (secrets.RELEASE_PLEASE_TOKEN): +# A tag/Release created with the built-in GITHUB_TOKEN does NOT trigger +# other workflows — GitHub suppresses those events to prevent infinite +# loops. If we used GITHUB_TOKEN here, build-and-release.yml would never +# fire on the new tag and every Release would ship with an empty asset +# list. The secret must be a PAT (or GitHub App token) with, on this repo, +# Contents: read/write + Pull requests: read/write. Setup steps are in +# docs/DEVELOPMENT.md -> "Releases" -> "Required secret: RELEASE_PLEASE_TOKEN". +on: + push: + branches: [main] + +# Governs the built-in GITHUB_TOKEN. release-please itself authenticates +# with the PAT passed as `token:` below, but the action still touches the +# default token for some calls, so keep these scopes granted. +permissions: + contents: write + pull-requests: write + issues: write + +# Serialise release-please runs so two quick pushes to main don't race on +# updating the same release PR. Don't cancel an in-flight run — letting it +# finish keeps the release PR consistent. +concurrency: + group: release-please-${{ github.ref }} + cancel-in-progress: false + +jobs: + release-please: + name: Maintain release PR / cut release + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..37fcefa --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.0" +} diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 8ea0894..b5468a0 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -447,11 +447,32 @@ Suite against the layer on your target runtime — see ## Releases -The GitHub Actions workflow -([`build-and-release.yml`](../.github/workflows/build-and-release.yml)) -builds `Release` and `Debug` x64 on every push to `main` (as a sanity -check), every PR, and every `v*.*.*` tag. On a tag push it -additionally creates a GitHub Release and attaches: +Releases are automated with +[release-please](https://github.com/googleapis/release-please) +([`release-please.yml`](../.github/workflows/release-please.yml)), +driven by [Conventional Commits](https://www.conventionalcommits.org/). +You never push a release tag by hand. + +**The flow:** + +1. Land Conventional-Commit commits on `main` — `feat:` → minor bump, + `fix:` → patch, `feat!:` / `BREAKING CHANGE:` → major. Commits + whose type isn't recognised (chore, docs, ci, …) don't appear in + the changelog and don't move the version. +2. release-please keeps an open **"chore: release X.Y.Z" PR** that + bumps [`version.txt`](../version.txt) and regenerates `CHANGELOG.md` + (created by the first release PR) from those commits. It rewrites + itself on every push to `main` — leave it alone until you're ready + to ship. +3. **Merging that PR is the release action.** release-please then + creates the `vX.Y.Z` tag and a GitHub Release whose body is the + generated changelog. +4. That tag triggers + [`build-and-release.yml`](../.github/workflows/build-and-release.yml), + which builds + signs `Release`/`Debug` x64 and **attaches** the + assets to the Release release-please just created — appending the + install instructions below the changelog rather than overwriting + it: - `XR_APILAYER_MLEDOUR_xr_telemetry--x64-Setup.exe` — Inno Setup installer (recommended for end users) @@ -460,18 +481,43 @@ additionally creates a GitHub Release and attaches: - `XR_APILAYER_MLEDOUR_xr_telemetry-Debug-x64.zip` — debug build with full symbols, for troubleshooting -To publish a new release: +`build-and-release.yml` still also builds `Release`/`Debug` on every +push to `main` and every PR as a verification gate (unsigned, no +Release) — that part is unchanged. -```bash -git tag v0.1.0 -git push origin v0.1.0 -``` - -The tag is derived into the DLL's `VERSIONINFO` resource at build +The version is derived into the DLL's `VERSIONINFO` resource at build time via -[`scripts/Generate-VersionRc.ps1`](../scripts/Generate-VersionRc.ps1), -and into the installer's filename and `AppVersion` via the -`/DMyAppVersion` flag passed to ISCC. +[`scripts/Generate-VersionRc.ps1`](../scripts/Generate-VersionRc.ps1) +(from the `v*.*.*` tag, falling back to `git describe`), and into the +installer's filename and `AppVersion` via the `/DMyAppVersion` flag +passed to ISCC. + +> **Bootstrapping:** +> [`.release-please-manifest.json`](../.release-please-manifest.json) +> is seeded at `1.0.0` to match the existing `v1.0.0` tag, so the +> first release PR bumps from there using the commits landed since. + +### Required secret: `RELEASE_PLEASE_TOKEN` + +release-please must tag using a token **other than** the built-in +`GITHUB_TOKEN`. GitHub deliberately does not let events created with +`GITHUB_TOKEN` trigger further workflows (an anti-recursion measure), +so a tag pushed with it would never start `build-and-release.yml` and +the Release would ship with **no assets**. Provide a token as the +`RELEASE_PLEASE_TOKEN` repository secret (Settings → Secrets and +variables → Actions → New repository secret): + +| Option | What to grant | +|--------|---------------| +| **Fine-grained PAT** (recommended) | This repo only; repository permissions **Contents: Read and write** + **Pull requests: Read and write** | +| **Classic PAT** | `repo` scope | +| **GitHub App token** | `contents: write` + `pull_requests: write` — no expiry, more setup | + +Because release-please authenticates with this PAT, the "Allow GitHub +Actions to create and approve pull requests" repo/org toggle is **not** +required (that setting only gates the default `GITHUB_TOKEN`). If the +secret is missing or under-scoped, the release-please job fails fast +with a 403 — it never silently produces an asset-less Release. ## Code signing @@ -629,6 +675,12 @@ echoed by `Sign-Artifact.ps1` and `Get-CertumTotp.ps1`, and the PowerShell scripts pass them as process arguments rather than through `cmd /c` so they don't leak into the shell-history transcript. +> These three are **signing** secrets and are all optional (each +> signing step skips cleanly when its secret is unset, yielding +> unsigned artifacts). Release **automation** needs a separate secret, +> [`RELEASE_PLEASE_TOKEN`](#required-secret-release_please_token), +> documented under [Releases](#releases) above. + Certum SimplySign uses 2FA where the **TOTP is the second factor** — there is no separate static password to set, so we don't need a `CERTUM_PASSWORD` secret. Username + freshly-generated TOTP is the diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..5086c17 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "include-component-in-tag": false, + "packages": { + ".": {} + } +} diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +1.0.0