[CI] Polish release naming and workflow env conventions

Rebased remainder of #7014 onto master, where the file has been rewritten
since the branch was opened. Keeps master's newer build definitions and
re-applies only the conventions that still apply:

- prep_release.sh emits a single beta=true/false output instead of is_beta
- the release step consumes it in UPPER_CASE env vars, passes --latest for
  stable releases, and fills an existing empty draft with the same flags
- docker.sh and the workflow agree on BUILD_REFERENCE and CACHE_DIR, so the
  Linux ccache and image cache are no longer written to a throwaway dir

The Windows asset suffix stays -Win10 (old clients match on that token) and
release_channel.cpp is dropped: master already matches Win|Windows.
This commit is contained in:
BruebachL 2026-11-08 09:15:17 +01:00
parent 1ca67ebb0f
commit b2596fd562
3 changed files with 63 additions and 61 deletions

View file

@ -7,18 +7,18 @@
# usage: source <script> <name> [--get] [--build] [--save] [--interactive] [--set-cache <location>] # usage: source <script> <name> [--get] [--build] [--save] [--interactive] [--set-cache <location>]
# <name> sets the name of the docker image, these correspond to directories in .ci # <name> sets the name of the docker image, these correspond to directories in .ci
# --get loads the image from a previously saved image cache, will build if no image is found # --get loads the image from a previously saved image cache, will build if no image is found
# --build builds the image from the Dockerfile in .ci/$NAME # --build builds the image from the Dockerfile in .ci/$BUILD_REFERENCE
# --save stores the image, if an image was loaded it will not be stored # --save stores the image, if an image was loaded it will not be stored
# --interactive immediately starts the image interactively for debugging # --interactive immediately starts the image interactively for debugging
# --set-cache <location> sets the location to cache the image or for ccache # --set-cache <location> sets the location to cache the image or for ccache
# #
# requires: docker # requires: docker
# uses env: NAME IMAGE_SUFFIX CACHE BUILD GET SAVE INTERACTIVE # uses env: BUILD_REFERENCE IMAGE_SUFFIX CACHE_DIR BUILD GET SAVE INTERACTIVE
# (correspond to args: <name> --set-cache <cache> --build --get --save --interactive) # (correspond to args: <name> --set-cache <cache> --build --get --save --interactive)
# sets env: RUN CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT # sets env: RUN CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT
# exitcode: 1 for failure, 2 for missing dockerfile, 3 for invalid arguments # exitcode: 1 for failure, 2 for missing dockerfile, 3 for invalid arguments
# #
# IMAGE_SUFFIX is optional and only appended to the image name, never to NAME, so that a matrix # IMAGE_SUFFIX is optional and only appended to the image name, never to BUILD_REFERENCE, so that a matrix
# leg can have its own image and image cache without needing its own directory under .ci. Two legs # leg can have its own image and image cache without needing its own directory under .ci. Two legs
# building and tagging the same image concurrently would race on `docker build --tag`. # building and tagging the same image concurrently would race on `docker build --tag`.
# It is deliberately not called SUFFIX: compile.sh already uses SUFFIX for output file names, and # It is deliberately not called SUFFIX: compile.sh already uses SUFFIX for output file names, and
@ -63,10 +63,10 @@ while [[ $# != 0 ]]; do
echo "--set-cache expects an argument" >&2 echo "--set-cache expects an argument" >&2
exit 3 exit 3
fi fi
CACHE=$1 CACHE_DIR=$1
shift shift
if ! [[ -d $CACHE ]]; then if ! [[ -d $CACHE_DIR ]]; then
echo "could not find cache path: $CACHE" >&2 echo "could not find cache path: $CACHE_DIR" >&2
return 3 return 3
fi fi
;; ;;
@ -75,45 +75,45 @@ while [[ $# != 0 ]]; do
echo "unrecognized option: $1" echo "unrecognized option: $1"
return 3 return 3
fi fi
NAME="$1" BUILD_REFERENCE="$1"
shift shift
;; ;;
esac esac
done done
# Setup # Setup
if ! [[ $NAME ]]; then if ! [[ $BUILD_REFERENCE ]]; then
echo "no build name given" >&2 echo "no build name given" >&2
return 3 return 3
fi fi
export IMAGE_NAME="${project_name,,}_${NAME,,}${IMAGE_SUFFIX:+_${IMAGE_SUFFIX,,}}" # lower case export IMAGE_NAME="${project_name,,}_${BUILD_REFERENCE,,}${IMAGE_SUFFIX:+_${IMAGE_SUFFIX,,}}" # lower case
docker_dir=".ci/$NAME" docker_dir=".ci/$BUILD_REFERENCE"
if ! [[ -r $docker_dir/Dockerfile ]]; then if ! [[ -r $docker_dir/Dockerfile ]]; then
echo "could not find Dockerfile in $docker_dir" >&2 echo "could not find Dockerfile in $docker_dir" >&2
return 2 # even if the image is cached, we do not want to run if there is no way to build this image return 2 # even if the image is cached, we do not want to run if there is no way to build this image
fi fi
if ! [[ $CACHE ]]; then if ! [[ $CACHE_DIR ]]; then
echo "cache dir is not set!" >&2 echo "cache dir is not set!" >&2
CACHE="$(mktemp -d)" CACHE_DIR="$(mktemp -d)"
echo "set cache dir to $CACHE" >&2 echo "set cache dir to $CACHE_DIR" >&2
fi fi
if ! [[ -d $CACHE ]]; then if ! [[ -d $CACHE_DIR ]]; then
echo "could not find cache dir: $CACHE" >&2 echo "could not find cache dir: $CACHE_DIR" >&2
mkdir -p "$CACHE" mkdir -p "$CACHE_DIR"
unset GET # the dir is empty unset GET # the dir is empty
fi fi
if [[ $GET || $SAVE ]]; then if [[ $GET || $SAVE ]]; then
img_dir="$CACHE/$image_cache" img_dir="$CACHE_DIR/$image_cache"
img_save="$img_dir/$IMAGE_NAME$save_extension" img_save="$img_dir/$IMAGE_NAME$save_extension"
if ! [[ -d $img_dir ]]; then if ! [[ -d $img_dir ]]; then
echo "could not find image dir: $img_dir" >&2 echo "could not find image dir: $img_dir" >&2
mkdir -p "$img_dir" mkdir -p "$img_dir"
fi fi
fi fi
export CCACHE_DIR="$CACHE/$ccache_cache" export CCACHE_DIR="$CACHE_DIR/$ccache_cache"
if ! [[ -d $CCACHE_DIR ]]; then if ! [[ -d $CCACHE_DIR ]]; then
echo "could not find ccache dir: $CCACHE_DIR" >&2 echo "could not find ccache dir: $CCACHE_DIR" >&2
mkdir -p "$CCACHE_DIR" mkdir -p "$CCACHE_DIR"

View file

@ -7,7 +7,7 @@
# adds output to GITHUB_OUTPUT # adds output to GITHUB_OUTPUT
template_path=".ci/release_template.md" template_path=".ci/release_template.md"
body_path="/tmp/release.md" body_path="/tmp/release.md"
beta_regex='beta' beta_regex='-beta'
name_regex='set\(GIT_TAG_RELEASENAME "([[:print:]]+)")' name_regex='set\(GIT_TAG_RELEASENAME "([[:print:]]+)")'
whitespace='^\s*$' whitespace='^\s*$'
@ -23,28 +23,29 @@ fi
# create title # create title
if [[ $TAG =~ $beta_regex ]]; then if [[ $TAG =~ $beta_regex ]]; then
echo "is_beta=yes" >>"$GITHUB_OUTPUT" beta=true
echo "beta=$beta" >>"$GITHUB_OUTPUT"
title="$TAG" title="$TAG"
echo "creating beta release '$title'" echo "Creating beta release '$title'"
elif [[ ! $(cat CMakeLists.txt) =~ $name_regex ]]; then elif [[ ! $(cat CMakeLists.txt) =~ $name_regex ]]; then
echo "::error file=$0::could not find releasename in CMakeLists.txt" echo "::error file=$0::Could not find releasename in CMakeLists.txt"
exit 1 exit 1
else else
echo "is_beta=no" >>"$GITHUB_OUTPUT" beta=false
echo "beta=$beta" >>"$GITHUB_OUTPUT"
name="${BASH_REMATCH[1]}" name="${BASH_REMATCH[1]}"
version="${TAG##*-}" version="${TAG##*-}"
title="Cockatrice $version: $name" title="Cockatrice $version: $name"
no_beta=1
echo "friendly_name=$name" >>"$GITHUB_OUTPUT" echo "friendly_name=$name" >>"$GITHUB_OUTPUT"
echo "creating full release '$title'" echo "Creating full release '$title'"
fi fi
echo "title=$title" >>"$GITHUB_OUTPUT" echo "title=$title" >>"$GITHUB_OUTPUT"
# add release notes template # add release notes template
if [[ $no_beta ]]; then if [[ $beta == "false" ]]; then
body="$(cat "$template_path")" body="$(cat "$template_path")"
if [[ ! $body ]]; then if [[ ! $body ]]; then
echo "::warning file=$0::could not find release template" echo "::warning file=$0::Could not find release template"
fi fi
body="${body//--REPLACE-WITH-RELEASE-TITLE--/$title}" body="${body//--REPLACE-WITH-RELEASE-TITLE--/$title}"
else else
@ -64,13 +65,13 @@ before="${all_tags%%
"$TAG"*}" # strip line with current tag an all lines after it "$TAG"*}" # strip line with current tag an all lines after it
# note the extra newlines are needed to always have a last line # note the extra newlines are needed to always have a last line
if [[ $all_tags == "$before" ]]; then if [[ $all_tags == "$before" ]]; then
echo "::warning file=$0::could not find current tag" echo "::warning file=$0::Could not find current tag"
else else
while while
previous="${before##* previous="${before##*
}" # get the last line }" # get the last line
# skip this tag if this is a full release and it's a beta or if empty # skip this tag if this is a full release and it's a beta or if empty
[[ $no_beta && $previous =~ $beta_regex || ! $previous ]] [[ $beta == "false" && $previous =~ $beta_regex || ! $previous ]]
do do
beta_list+=" $previous" # add to list of skipped betas beta_list+=" $previous" # add to list of skipped betas
next_before="${before% next_before="${before%
@ -85,7 +86,7 @@ else
if generated_list="$(git log "$previous..$TAG" --pretty="- %s")"; then if generated_list="$(git log "$previous..$TAG" --pretty="- %s")"; then
count="$(git rev-list --count "$previous..$TAG")" count="$(git rev-list --count "$previous..$TAG")"
[[ $previous =~ $beta_regex ]] && previousreleasetype="beta release" || previousreleasetype="full release" [[ $previous =~ $beta_regex ]] && previousreleasetype="beta release" || previousreleasetype="full release"
echo "adding list of commits to release notes:" echo "Adding list of commits to release notes:"
echo "'$previous' to '$TAG' ($count commits)" echo "'$previous' to '$TAG' ($count commits)"
# --> is the markdown comment escape sequence, emojis are way better # --> is the markdown comment escape sequence, emojis are way better
generated_list="${generated_list//-->/→}" generated_list="${generated_list//-->/→}"
@ -98,15 +99,15 @@ else
if [[ $beta_list =~ $whitespace ]]; then if [[ $beta_list =~ $whitespace ]]; then
beta_list="-n there are no betas to delete!" beta_list="-n there are no betas to delete!"
else else
echo "the following betas should be deleted after publishing:" echo "The following betas should be deleted after publishing:"
echo "$beta_list" echo "$beta_list"
fi fi
body="${body//--REPLACE-WITH-BETA-LIST--/$beta_list}" body="${body//--REPLACE-WITH-BETA-LIST--/$beta_list}"
else else
echo "::warning file=$0::failed to produce git log" echo "::warning file=$0::Failed to produce git log"
fi fi
else else
echo "::warning file=$0::could not find previous tag" echo "::warning file=$0::Could not find previous tag"
fi fi
fi fi

View file

@ -108,41 +108,42 @@ jobs:
id: create_release id: create_release
shell: bash shell: bash
env: env:
BODY_PATH: ${{ steps.prepare.outputs.body_path }}
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
tag_name: ${{ steps.configure.outputs.tag }} PRERELEASE: ${{ steps.prepare.outputs.beta }}
target: ${{ steps.configure.outputs.sha }} RELEASE_NAME: ${{ steps.prepare.outputs.title }}
release_name: ${{ steps.prepare.outputs.title }} TAG_NAME: ${{ steps.configure.outputs.tag }}
body_path: ${{ steps.prepare.outputs.body_path }} TARGET: ${{ steps.configure.outputs.sha }}
prerelease: ${{ steps.prepare.outputs.is_beta }}
run: | run: |
# A release for this tag may already exist: a maintainer can save a draft in # A release for this tag may already exist: a maintainer can save a draft in
# the GitHub UI before pushing the tag, or an earlier run may have left one. # the GitHub UI before pushing the tag, or an earlier run may have left one.
# Never touch a published release, and never overwrite a draft that already # Never touch a published release, and never overwrite a draft that already
# has a body (generated notes or a maintainer's edits) - only fill in an # has a body (generated notes or a maintainer's edits) - only fill in an
# empty draft. Prints "<isDraft> <body length>"; empty when there is none. # empty draft. Prints "<isDraft> <body length>"; empty when there is none.
release_state="$(gh release view "$tag_name" --json isDraft,body \ release_state="$(gh release view "$TAG_NAME" --json isDraft,body \
--template '{{.isDraft}} {{len (or .body "")}}' 2>/dev/null || true)" --template '{{.isDraft}} {{len (or .body "")}}' 2>/dev/null || true)"
# A stable release is explicitly marked as latest: the automatic choice is
# based on the publish date and version, which a delayed draft or a beta
# would decide for us.
args=()
[[ "$PRERELEASE" == "true" ]] && args+=(--prerelease)
[[ "$PRERELEASE" == "false" ]] && args+=(--latest)
if [[ -z "$release_state" ]]; then if [[ -z "$release_state" ]]; then
args=() gh release create "$TAG_NAME" --verify-tag --draft "${args[@]}" \
[[ $prerelease == yes ]] && args+=(--prerelease) --target "$TARGET" \
--title "$RELEASE_NAME" \
gh release create "$tag_name" --verify-tag --draft "${args[@]}" \ --notes-file "$BODY_PATH"
--target "$target" \
--title "$release_name" \
--notes-file "$body_path"
elif [[ "$release_state" == "true 0" ]]; then elif [[ "$release_state" == "true 0" ]]; then
echo "Filling in the empty draft release $tag_name" echo "Filling in the empty draft release $TAG_NAME"
args=(--prerelease=false) gh release edit "$TAG_NAME" "${args[@]}" \
[[ $prerelease == yes ]] && args=(--prerelease) --title "$RELEASE_NAME" \
--notes-file "$BODY_PATH"
gh release edit "$tag_name" "${args[@]}" \
--title "$release_name" \
--notes-file "$body_path"
elif [[ "$release_state" == true* ]]; then elif [[ "$release_state" == true* ]]; then
echo "::warning::Draft release $tag_name already has a body, leaving it untouched" echo "::warning::Draft release $TAG_NAME already has a body, leaving it untouched"
else else
echo "::warning::Release $tag_name is already published, leaving it untouched" echo "::warning::Release $TAG_NAME is already published, leaving it untouched"
fi fi
vcpkg-baseline: vcpkg-baseline:
@ -245,7 +246,7 @@ jobs:
continue-on-error: ${{ matrix.allow-failure == 'yes' }} continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70 timeout-minutes: 70
env: env:
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache CACHE_DIR: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d CCACHE_EVICTION_AGE: 7d
# The bound is sized against the repository's 10 GB cache quota # The bound is sized against the repository's 10 GB cache quota
# (https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy), # (https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy),
@ -315,7 +316,7 @@ jobs:
# is slower, which is accepted because a tag is rare and only pull requests gate. # is slower, which is accepted because a tag is rare and only pull requests gate.
COCKATRICE_DEBUG_INFO_LEVEL: ${{ github.ref_type != 'tag' && '1' || '' }} COCKATRICE_DEBUG_INFO_LEVEL: ${{ github.ref_type != 'tag' && '1' || '' }}
CMAKE_GENERATOR: 'Ninja' CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }} BUILD_REFERENCE: ${{ matrix.distro }}${{ matrix.version }}
IMAGE_SUFFIX: ${{ matrix.variant }} IMAGE_SUFFIX: ${{ matrix.variant }}
# Which build the suite runs in, decided once per leg here so the steps' if: conditions # Which build the suite runs in, decided once per leg here so the steps' if: conditions
# and their shells cannot drift apart. A step's own env is not visible to its if:, which # and their shells cannot drift apart. A step's own env is not visible to its if:, which
@ -345,7 +346,7 @@ jobs:
uses: actions/cache/restore@v6 uses: actions/cache/restore@v6
with: with:
key: ccache-${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} key: ccache-${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }}
path: ${{ env.CACHE }}/.ccache path: ${{ env.CACHE_DIR }}/.ccache
- name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image" - name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash shell: bash
@ -510,7 +511,7 @@ jobs:
uses: actions/cache/save@v6 uses: actions/cache/save@v6
with: with:
key: ${{ steps.restore_ccache.outputs.cache-primary-key }} key: ${{ steps.restore_ccache.outputs.cache-primary-key }}
path: ${{ env.CACHE }}/.ccache path: ${{ env.CACHE_DIR }}/.ccache
- name: "Upload artifact" - name: "Upload artifact"
id: upload_artifact id: upload_artifact
@ -623,7 +624,7 @@ jobs:
continue-on-error: ${{ matrix.allow-failure == 'yes' }} continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70 timeout-minutes: 70
env: env:
CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache CACHE_DIR: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d CCACHE_EVICTION_AGE: 7d
# These legs do not keep a compiler cache: they run once a day (and on tags) and gate # These legs do not keep a compiler cache: they run once a day (and on tags) and gate
# nothing, so their share of the 10 GB quota goes to the pull-request legs instead. # nothing, so their share of the 10 GB quota goes to the pull-request legs instead.
@ -632,7 +633,7 @@ jobs:
PERSIST_CCACHE: 'false' PERSIST_CCACHE: 'false'
CCACHE_SIZE: ${{ matrix.ccache_size || '600M' }} CCACHE_SIZE: ${{ matrix.ccache_size || '600M' }}
CMAKE_GENERATOR: 'Ninja' CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }} BUILD_REFERENCE: ${{ matrix.distro }}${{ matrix.version }}
IMAGE_SUFFIX: ${{ matrix.variant }} IMAGE_SUFFIX: ${{ matrix.variant }}
# The shared steps below gate on these, so they have to be derived here too. Left # The shared steps below gate on these, so they have to be derived here too. Left
# undefined they silently skipped the build the leg exists for: the coverage leg # undefined they silently skipped the build the leg exists for: the coverage leg