diff --git a/.github/check-version.sh b/.github/check-version.sh index bb69b16..96f5b4a 100755 --- a/.github/check-version.sh +++ b/.github/check-version.sh @@ -2,38 +2,57 @@ # # Copyright (c) OpenIPC https://openipc.org MIT License # -# check-version.sh — fail a pull request that changes code or the build -# but not PROGRAM_VERSION in vectis.c. The new version must be -# MAJOR.MINOR.PATCH without leading zeros, greater than the base one, -# and have its section in CHANGELOG.md ("## — "). -# The sources, the Makefile and ci/ count as code or build; docs, tests, -# screenshots and the workflows in .github/ do not need a new version. +# check-version.sh — fail a pull request that changes the code or the build +# but not PROGRAM_VERSION in vectis.c. 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. The sources, the Makefile, ci/ and the +# build workflow (.github/workflows/build.yml: it builds the release files) +# count as code or build, without the Markdown files in them; docs, tests, +# screenshots and the rest of .github/ 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 in the +# workflow, 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="vectis.c vectis-bootrom.c usbserial.c usbserial.h ch34x.c Makefile ci" +source=vectis.c +code="vectis.c vectis-bootrom.c usbserial.c usbserial.h ch34x.c Makefile ci .github/workflows/build.yml" -ver() { sed -n 's/^#define PROGRAM_VERSION *"\(.*\)"/\1/p'; } - -new=$(ver < vectis.c) -old=$(git show "$base:vectis.c" | ver) +# The one PROGRAM_VERSION definition (comments and other text do not count). +ver() { + v=$(sed -n 's/^#define PROGRAM_VERSION[[:space:]]*"\([^"]*\)"[[:space:]]*$/\1/p') + if [ "$(printf '%s\n' "$v" | grep -c .)" -ne 1 ]; then + echo "::error::$1:$source: expected one PROGRAM_VERSION definition," \ + "found: $(printf '%s' "$v" | tr '\n' ' ')" >&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::PROGRAM_VERSION \"$new\" is not MAJOR.MINOR.PATCH" - exit 1 -fi +new=$(ver HEAD < "$source") +old=$(git show "$base:$source" | 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, PROGRAM_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, PROGRAM_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 @@ -41,6 +60,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::PROGRAM_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::PROGRAM_VERSION $new is not greater than $old" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c3d587..9b13df7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,8 +8,11 @@ # (ci/build-static.sh) and soft-float mipsel for OpenWrt routers # (ci/build-mipsel.sh). # On pull requests, .github/check-version.sh checks that a change to the -# code or the build comes with a new PROGRAM_VERSION and its CHANGELOG.md -# section. +# code or the build (this workflow included) comes with a new +# PROGRAM_VERSION and its CHANGELOG.md section; the no-release label lets +# a change that keeps the release files as they are through without one. +# Only the head of master publishes, so a re-run of an older run never +# rolls "latest" back. # Release: one release only, "latest", replaced by every push to master # once all builds pass. Its title carries PROGRAM_VERSION ("Vectis 1.8.0"), # its notes are short; the history is in CHANGELOG.md. No versioned @@ -22,6 +25,8 @@ name: Build on: push: pull_request: + # labeled / unlabeled: the version check reads the no-release label + types: [opened, synchronize, reopened, labeled, unlabeled] workflow_dispatch: # The build only reads the repository and uploads artifacts. @@ -46,11 +51,12 @@ jobs: 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: - # through the environment, not pasted into the script - BASE: ${{ github.base_ref }} - run: .github/check-version.sh "origin/$BASE" + # 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 linux: name: Linux ${{ matrix.platform.name }} @@ -229,6 +235,18 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | + # Only the head of master publishes: a re-run of an older run must not + # roll "latest" back to its commit. Checked here, right before the + # release is touched; a newer push's run waits for this one (concurrency) + # and publishes after it. A failed lookup fails the job, not skips it. + if ! head=$(git ls-remote --exit-code origin refs/heads/master); then + echo "::error::cannot read the head of master"; exit 1 + fi + head=${head%%[[:space:]]*} + if [ "$head" != "$GITHUB_SHA" ]; then + echo "::notice::master is at ${head:0:7} now, not ${GITHUB_SHA::7}: not published" + exit 0 + fi # The release being replaced: its title tells Telegram whether this # is a new version. "Not found" means there is none yet; any other # failure stops here, before anything is deleted or announced.