diff --git a/.github/check-version.sh b/.github/check-version.sh index 8e3e9bb..ad0cf91 100755 --- a/.github/check-version.sh +++ b/.github/check-version.sh @@ -2,39 +2,61 @@ # # Copyright (c) OpenIPC https://openipc.org MIT License # -# check-version.sh — fail a pull request that changes code or the build -# but not CAMEX_VERSION in src/version.h (#63). The new version must be -# MAJOR.MINOR.PATCH without leading zeros, greater than the base one, -# and have its section in CHANGELOG.md ("## — "). -# src/, android/, the Makefile, ci/ and scripts/ count as code or build; -# docs, tests, screenshots and the workflows in .github/ do not need a new -# version. Same script as OpenIPC/vectis, other paths and version macro. +# check-version.sh — fail a pull request that changes the code or the build +# but not CAMEX_VERSION in src/version.h. A new version must be +# MAJOR.MINOR.PATCH without leading zeros, greater than the base one, and +# have its section in CHANGELOG.md ("## — YYYY-MM-DD") that the +# base branch does not have yet. src/, android/, the Makefile, ci/, +# scripts/ and the build workflow (.github/workflows/build.yml) count as code +# or build, without the Markdown files in them; docs, tests, screenshots and +# the rest of .github/ (this check and its workflow, version.yml, included) +# need no new version. NO_RELEASE=true (the pull request's `no-release` +# label) lets a change to the code or the build through without a new +# version, for changes that leave the release files as they are (a test or +# publication step, a build-script tidy-up, a comment). # -# Usage: .github/check-version.sh [BASE] (default origin/master) +# Usage: .github/check-version.sh [BASE] (default origin/master; CI passes +# HEAD^1, the base side of the pull request's merge commit) # set -eu base=${1:-origin/master} -code="src android Makefile ci scripts" +header=src/version.h +code="src android Makefile ci scripts .github/workflows/build.yml" -ver() { sed -n 's/^#define CAMEX_VERSION *"\(.*\)"/\1/p'; } - -new=$(ver < src/version.h) -old=$(git show "$base:src/version.h" | ver) +# The one CAMEX_VERSION definition. Every "#define CAMEX_VERSION" counts, +# however it is spaced ("# define" too), and it must be the plain form +# '#define CAMEX_VERSION "X.Y.Z"' that the release job reads. +ver() { + h=$(cat) + n=$(printf '%s\n' "$h" | grep -cE '^[[:space:]]*#[[:space:]]*define[[:space:]]+CAMEX_VERSION([[:space:]]|$)' || true) + v=$(printf '%s\n' "$h" | sed -n 's/^#define CAMEX_VERSION "\([^"]*\)"[[:space:]]*$/\1/p') + if [ "$n" -ne 1 ] || [ -z "$v" ]; then + echo "::error::$1:$header: expected one '#define CAMEX_VERSION \"X.Y.Z\"'," \ + "found $n definition(s)" >&2 + exit 1 + fi + printf '%s\n' "$v" +} -if ! echo "$new" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'; then - echo "::error::CAMEX_VERSION \"$new\" is not MAJOR.MINOR.PATCH" - exit 1 -fi +new=$(ver HEAD < "$header") +old=$(git show "$base:$header" | ver "$base") # shellcheck disable=SC2086 # $code is a list of paths -changed=$(git diff --name-only "$base"...HEAD -- $code) +changed=$(git diff --name-only "$base"...HEAD -- $code ':(exclude,glob)**/*.md') if [ "$new" = "$old" ]; then + if [ -n "$changed" ] && [ "${NO_RELEASE:-}" = true ]; then + echo "::notice::code or build changed, CAMEX_VERSION stays $old: the no-release" \ + "label says the release files do not change" + echo "$changed" + exit 0 + fi if [ -n "$changed" ]; then echo "::error::code or build changed, CAMEX_VERSION is still $old:" \ - "bump PATCH (fix) or MINOR (feature) and add its CHANGELOG.md section" + "bump PATCH (fix) or MINOR (feature) and add its CHANGELOG.md section," \ + "or label the pull request no-release if the release files do not change" echo "$changed" exit 1 fi @@ -42,6 +64,11 @@ if [ "$new" = "$old" ]; then exit 0 fi +if ! echo "$new" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'; then + echo "::error::CAMEX_VERSION \"$new\" is not MAJOR.MINOR.PATCH" + exit 1 +fi + top=$(printf '%s\n%s\n' "$old" "$new" | sort -V | tail -n 1) if [ "$top" != "$new" ]; then echo "::error::CAMEX_VERSION $new is not greater than $old" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d2bc133..ea01acd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,24 +34,8 @@ env: # actions target Node 24 natively (checkout@v5, upload-artifact@v5, download-artifact@v5, gh-release@v3) jobs: - # ---------------------------------------------------------------- version - # Pull requests: a change to the code or the build comes with a new - # CAMEX_VERSION (MAJOR.MINOR.PATCH) and its CHANGELOG.md section (#63) - version: - name: Version bump - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Code changes come with a new version - env: - # through the environment, not pasted into the script - BASE: ${{ github.base_ref }} - run: .github/check-version.sh "origin/$BASE" + # The version check of pull requests runs in version.yml: a label change + # re-runs only that check, not the builds. # ------------------------------------------------------------------ linux linux: diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 0000000..428f94a --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,43 @@ +# +# Copyright (c) OpenIPC https://openipc.org MIT License +# +# version.yml — the version check of pull requests (#63, #68, #69) +# +# A change to the code or the build (build.yml included) comes with a new +# CAMEX_VERSION (MAJOR.MINOR.PATCH) and its CHANGELOG.md section, unless the +# no-release label says the release files do not change. A workflow of its +# own: adding or removing a label re-runs this check, not the builds. +# + +name: version + +on: + pull_request: + branches: [master, main] + # labeled / unlabeled: the check reads the no-release label + types: [opened, synchronize, reopened, labeled, unlabeled] + +permissions: + contents: read + +# a newer event of the same pull request cancels the older check +concurrency: + group: version-${{ github.ref }} + cancel-in-progress: true + +jobs: + version: + name: Version bump + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + # The checkout is the merge commit; its first parent is the base it was made from. + - name: Code changes come with a new version + env: + # a change that leaves the release files as they are may skip the new version + NO_RELEASE: ${{ contains(github.event.pull_request.labels.*.name, 'no-release') }} + run: .github/check-version.sh HEAD^1