Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 120 additions & 82 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,48 @@ name: Create Release
on:
workflow_dispatch:
inputs:
previous_release:
description: 'Existing tag to release from'
required: true
type: string
bump:
description: Version bump
description: 'Version bump -- required when Previous Release is a stable tag; leave "none" when Previous Release is an RC (to continue its RC series or promote it to stable)'
required: true
type: choice
options:
- none
- patch
- minor
- major

permissions:
contents: write

concurrency:
group: release
cancel-in-progress: false
create_rc:
description: 'Create RC tag (Note: unchecked = stable release / promote an existing RC to stable)'
required: false
type: boolean
default: false

jobs:
release:
if: github.ref_type == 'branch' && (github.ref_name == 'main' || endsWith(github.ref_name, '.rc') || endsWith(github.ref_name, '.x-maintenance'))
runs-on: ubuntu-latest
concurrency:
group: release
cancel-in-progress: false
permissions:
contents: write
issues: write
pull-requests: write
Comment thread
Copilot marked this conversation as resolved.

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ssh-key: ${{ secrets.RDKCM_DEPLOY_KEY }}

- name: Validate release branch
shell: bash
run: |
branch="${GITHUB_REF_NAME}"

case "$branch" in
main|*.rc|*.*.x-maintenance)
echo "Release allowed from branch: $branch"
Expand All @@ -47,73 +58,96 @@ jobs:
- name: Compute next version
id: version
env:
PREV: ${{ inputs.previous_release }}
BUMP: ${{ inputs.bump }}
CREATE_RC: ${{ inputs.create_rc }}
shell: bash
run: |
git fetch --tags --force origin

# Only consider strict vMAJOR.MINOR.PATCH tags reachable from HEAD;
# pre-release/other v-prefixed tags (e.g. v0.6.4.rc1) would break the
# numeric parsing below, and tags from unrelated history shouldn't
# count as "latest" for this release.
semver_tags() {
git tag --merged HEAD --list 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$'
}

# Reuse an existing HEAD tag if this is a rerun after a partial failure.
existing="$(git tag --points-at HEAD --list 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n1)"
if [ -n "$existing" ]; then
echo "HEAD already tagged as ${existing}; resuming partial release."
prev="$(semver_tags | awk -v ex="${existing}" '$0 != ex { print; exit }')"
[ -z "$prev" ] && prev="v0.0.0"
echo "latest=$prev" >> "$GITHUB_OUTPUT"
echo "next=$existing" >> "$GITHUB_OUTPUT"
exit 0
if ! git tag --list "$PREV" | grep -qxF "$PREV"; then
echo "ERROR: '${PREV}' is not an existing tag on this repo. Check Previous Release for typos."
exit 1
fi

latest="$(semver_tags | head -n1)"

if [ -z "$latest" ]; then
latest="v0.0.0"
fi
# Bump a vX.Y.Z string by the requested level
bump_stable() {
local ver="${1#v}" major minor patch
IFS='.' read -r major minor patch <<< "$ver"
case "$2" in
major) echo "v$((major+1)).0.0" ;;
minor) echo "v${major}.$((minor+1)).0" ;;
patch) echo "v${major}.${minor}.$((patch+1))" ;;
esac
}

ver="${latest#v}"
IFS='.' read -r major minor patch <<< "$ver"
is_rc=false
is_promotion=false
next=""

case "$BUMP" in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
patch)
patch=$((patch + 1))
;;
esac
if [[ "$PREV" =~ ^v[0-9]+\.[0-9]+\.[0-9]+\.rc[0-9]+$ ]]; then
# Previous release is itself an RC -- no bump makes sense here;
# either continue its RC series or promote it straight to stable.
if [ "$BUMP" != "none" ]; then
echo "ERROR: A version bump isn't supported when Previous Release is an RC tag (${PREV}). Select its underlying stable tag instead."
exit 1
fi
base_stable="${PREV%%.rc*}"
if [ "$CREATE_RC" = "true" ]; then
rc_num="${PREV##*.rc}"
next="${base_stable}.rc$((rc_num + 1))"
is_rc=true
else
next="$base_stable"
is_promotion=true
fi

elif [[ "$PREV" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# Previous release is a stable tag -- a bump is required to know
# what comes next.
if [ "$BUMP" = "none" ]; then
echo "ERROR: Select a version bump to release from stable tag ${PREV}."
exit 1
fi
next_stable="$(bump_stable "$PREV" "$BUMP")"
if [ "$CREATE_RC" = "true" ]; then
next="${next_stable}.rc1"
is_rc=true
else
next="$next_stable"
fi

next="v${major}.${minor}.${patch}"
else
echo "ERROR: '${PREV}' doesn't look like a release tag (expected vX.Y.Z or vX.Y.Z.rcN)."
exit 1
fi

echo "latest=$latest"
echo "next=$next"
echo "latest=$latest" >> "$GITHUB_OUTPUT"
echo "next=$next" >> "$GITHUB_OUTPUT"
echo "baseline=$PREV" | tee -a "$GITHUB_OUTPUT"
echo "next=$next" | tee -a "$GITHUB_OUTPUT"
echo "is_rc=$is_rc" | tee -a "$GITHUB_OUTPUT"
echo "is_promotion=$is_promotion" | tee -a "$GITHUB_OUTPUT"

- name: Validate release has changes
env:
LATEST: ${{ steps.version.outputs.latest }}
BASELINE: ${{ steps.version.outputs.baseline }}
IS_PROMOTION: ${{ steps.version.outputs.is_promotion }}
shell: bash
run: |
if [ "$LATEST" = "v0.0.0" ]; then
echo "No previous tag found; proceeding with initial release."
if [ "$IS_PROMOTION" = "true" ]; then
if ! git merge-base --is-ancestor "${BASELINE}" HEAD; then
echo "ERROR: ${BASELINE} is not an ancestor of HEAD; select a descendant commit from this branch before promoting to stable."
exit 1
fi
echo "Promoting ${BASELINE} to stable release; current HEAD is a descendant."
exit 0
fi

if git rev-list --count "${LATEST}..HEAD" | grep -q '^0$'; then
echo "No new commits since ${LATEST}; refusing to create a duplicate release."
if ! git merge-base --is-ancestor "${BASELINE}" HEAD; then
echo "ERROR: ${BASELINE} is not an ancestor of HEAD; select a Previous Release tag from this branch."
exit 1
fi
count="$(git rev-list --count "${BASELINE}..HEAD")"
if [ "$count" -eq 0 ]; then
echo "ERROR: No new commits since ${BASELINE}; refusing to create a duplicate release."
exit 1
fi

Expand All @@ -123,11 +157,12 @@ jobs:
shell: bash
run: |
if grep -q '^## \[Unreleased\]' CHANGELOG.md; then
echo "ERROR: CHANGELOG.md still has an [Unreleased] section. Update it before releasing."
echo "ERROR: CHANGELOG.md has an [Unreleased] section. Update it before releasing."
exit 1
fi
version="${NEXT#v}"
top="$(awk 'match($0, /^## \[([^]]+)\]/, m) { print m[1]; exit }' CHANGELOG.md)"
version="${version%%.rc*}"
top="$(awk '/^## \[[^]]+\]/ { sub(/^## \[/, ""); sub(/\].*$/, ""); print; exit }' CHANGELOG.md)"
if [ -z "$top" ] || [ "$top" != "$version" ]; then
echo "ERROR: Top CHANGELOG.md entry (${top:-<none>}) does not match version ${version}. Move the ${version} entry to the top."
exit 1
Expand All @@ -136,16 +171,24 @@ jobs:
- name: Create tag
env:
TAG: ${{ steps.version.outputs.next }}
GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
shell: bash
run: |
if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
echo "Tag ${TAG} already exists on origin; continuing."
git fetch --force origin "refs/tags/${TAG}:refs/tags/${TAG}"
if [ "$(git rev-parse "refs/tags/${TAG}^{commit}")" != "$(git rev-parse HEAD)" ]; then
echo "ERROR: Tag ${TAG} already exists on origin but does not point to the current HEAD; refusing to overwrite its release assets."
exit 1
fi
echo "Tag ${TAG} already exists at the current HEAD; resuming the release."
exit 0
fi
Comment thread
swethasukumarr marked this conversation as resolved.

git config user.name "github-actions"
git config user.email "github-actions@github.com"

# Identity is just for the annotated tag's metadata -- SEMANTIC_RELEASE_TOKEN is
# fine for this lookup, it doesn't touch ref creation.
author_login="$(gh api user --jq '.login')"
author_name="$(gh api user --jq '.name // .login')"
git config user.name "$author_name"
git config user.email "${author_login}@users.noreply.github.com"
git tag -a "${TAG}" -m "Release ${TAG}"
git push origin "${TAG}"

Expand All @@ -155,36 +198,31 @@ jobs:
VERSION: ${{ steps.version.outputs.next }}
shell: bash
run: |
# Reuse the existing script: strips .github/, writes .version, produces build/firebolt-cpp-client-<ver>.tar.gz
bare="${VERSION#v}"
./.github/scripts/mk-release-package.sh \
--version "${VERSION}" \
--version "${bare}" \
--package firebolt-cpp-client

bare="${VERSION#v}"
archive_name="firebolt-cpp-client-${bare}.tar.gz"
checksum_name="${archive_name}.sha256"

sha256sum "build/${archive_name}" > "build/${checksum_name}"

# Extract the top CHANGELOG.md section as release notes (same as the old generateNotesCmd).
awk '/^## / { if (s) exit; s=1 } s{ print }' CHANGELOG.md > release_notes.md

echo "archive=build/${archive_name}" >> "$GITHUB_OUTPUT"
echo "checksum=build/${checksum_name}" >> "$GITHUB_OUTPUT"
echo "archive=build/firebolt-cpp-client-${bare}.tar.gz" >> "$GITHUB_OUTPUT"

- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
TAG: ${{ steps.version.outputs.next }}
IS_RC: ${{ steps.version.outputs.is_rc }}
ARCHIVE: ${{ steps.asset.outputs.archive }}
CHECKSUM: ${{ steps.asset.outputs.checksum }}
shell: bash
run: |
release_flags=()
[ "$IS_RC" = "true" ] && release_flags+=(--prerelease)
if gh release view "$TAG" >/dev/null 2>&1; then
gh release upload "$TAG" "$ARCHIVE" "$CHECKSUM" --clobber
gh release upload "$TAG" "$ARCHIVE" --clobber
else
# The tag already exists (pushed in "Create tag"), so this only
# creates the release object against it.
gh release create "$TAG" \
--title "$TAG" \
--notes-file release_notes.md \
"$ARCHIVE" "$CHECKSUM"
"${release_flags[@]}" \
"$ARCHIVE"
fi
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- `Localization.timeZone` getter and `onTimeZoneChanged` event
- `Device.dolbyAtmosExperienceAvailable` getter and `onDolbyAtmosExperienceAvailableChanged` event
- `Device.osName`, `Device.osVersion`, and `Device.firmware`
- `SpeechSynthesis` (`speak`, `cancel`, `pause`, `resume`, `voices`, `subscribeVoiceChanged`, `onUtteranceEvent`)
- `SpeechSynthesis` (`speak`, `cancel`, `pause`, `resume`, `voices`, `subscribeOnVoicesChanged`, `onUtteranceEvent`)
- `VideoOutput` implementation

### Changed
Expand Down
Loading