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
84 changes: 84 additions & 0 deletions .github/workflows/release-prep-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Regression test for the release source archive preparation script.
#
# Only runs when release_prep.sh or MODULE.bazel changes (path filter).
# Not part of the main gate — doesn't block unrelated PRs.
#
# Validates:
# - Archive naming (includes 'v' prefix)
# - MODULE.bazel version patching (only module() block, not bazel_dep)
# - Release notes output
# - Graceful failures (no args, invalid tag format)

name: Release prep test

on:
pull_request:
paths:
- ".github/workflows/release_prep.sh"
- ".github/workflows/release-prep-test.yaml"
- "MODULE.bazel"
push:
branches: [main]
paths:
- ".github/workflows/release_prep.sh"
- ".github/workflows/release-prep-test.yaml"
- "MODULE.bazel"

permissions:
contents: read

jobs:
test:
name: Validate release_prep.sh
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0

- name: Test with synthetic tag
run: |
.github/workflows/release_prep.sh v99.0.0 > /tmp/notes.txt

# Assert archive name
test -f rules_odin-v99.0.0.tar.gz

# Assert MODULE.bazel version patched inside module() block
MODULE=$(tar -xzf rules_odin-v99.0.0.tar.gz -O rules_odin-99.0.0/MODULE.bazel)
echo "$MODULE" | sed -n '/^module(/,/^)/p' | grep -q 'version = "99.0.0"'

# Assert bazel_dep versions NOT changed
echo "$MODULE" | grep -q 'version = "1.7.1"'
echo "$MODULE" | grep -q 'version = "0.0.10"'

# Assert release notes mention correct version
grep -q 'version = "99.0.0"' /tmp/notes.txt

rm -f rules_odin-v99.0.0.tar.gz
echo "Happy path: PASSED"

- name: Test no-arg invocation fails
run: |
if .github/workflows/release_prep.sh 2>/dev/null; then
echo "::error::Expected failure with no args"
exit 1
fi
echo "No-arg rejection: PASSED"

- name: Test invalid tag format fails
run: |
if .github/workflows/release_prep.sh "not-a-tag" 2>/dev/null; then
echo "::error::Expected failure with invalid tag 'not-a-tag'"
exit 1
fi

if .github/workflows/release_prep.sh "main" 2>/dev/null; then
echo "::error::Expected failure with branch name 'main'"
exit 1
fi

if .github/workflows/release_prep.sh "0.2.0" 2>/dev/null; then
echo "::error::Expected failure with tag missing 'v' prefix"
exit 1
fi
echo "Invalid tag rejection: PASSED"
67 changes: 52 additions & 15 deletions .github/workflows/release_prep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,74 @@
# Creates the release source archive and outputs release notes.
# The archive name MUST match the glob in release.yaml: rules_odin-*.tar.gz
#
# Environment variables available:
# TAG — the version tag being released (e.g., v0.2.0)
# GITHUB_WORKSPACE — repo checkout root
# Called as: .github/workflows/release_prep.sh ${{ inputs.tag_name || github.ref_name }}
# The tag is passed as the first positional argument (not as an env var).

set -euo pipefail
set -o errexit -o nounset -o pipefail

TAG="${TAG:-${GITHUB_REF_NAME:-}}"
# Argument provided by release_ruleset.yaml.
TAG="${1:-}"

if [[ -z "$TAG" ]]; then
echo "::error::TAG is not set. Cannot determine release version."
exit 1
fi

# Strip the 'v' prefix for the archive name (e.g., v0.2.0 → 0.2.0)
# Validate tag format (semver with 'v' prefix).
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "::error::TAG '${TAG}' does not match expected format (vMAJOR.MINOR.PATCH)."
exit 1
fi

# Strip the 'v' prefix for module version (v0.2.0 -> 0.2.0)
VERSION="${TAG#v}"

ARCHIVE="rules_odin-${VERSION}.tar.gz"
# Archive name includes the 'v' prefix to match GitHub's source archive naming.
# This allows users to switch between released artifacts and source archives
# with minimal differences (strip_prefix stays the same).
PREFIX="rules_odin-${VERSION}"
ARCHIVE="rules_odin-${TAG}.tar.gz"

echo "Creating release archive: ${ARCHIVE}"

# Create a reproducible source archive using git archive.
# --prefix ensures the extracted directory is named rules_odin-{VERSION}/
git archive \
--format=tar.gz \
--prefix="rules_odin-${VERSION}/" \
--output="${ARCHIVE}" \
HEAD
# Create the archive via git archive, then patch MODULE.bazel in-place.
# smlx/ccv tags HEAD as-is, so MODULE.bazel still has the old version.
# The BCR validation requires the version to match the tag exactly.
#
# We extract the full archive, patch MODULE.bazel, then re-archive.
# This avoids GNU tar --delete/--append which is not available on macOS bsdtar.
TMPDIR=$(mktemp -d)
trap 'rm -rf "${TMPDIR}"' EXIT

git archive --format=tar --prefix="${PREFIX}/" HEAD | tar -xf - -C "${TMPDIR}"

# Patch the version in MODULE.bazel to match the tag.
# Only replace the version inside the module() call, not bazel_dep versions.
# The module() block range ensures we don't touch dependency versions.
sed -i.bak "/^module(/,/^)/s/version = \"[^\"]*\"/version = \"${VERSION}\"/" "${TMPDIR}/${PREFIX}/MODULE.bazel"
rm -f "${TMPDIR}/${PREFIX}/MODULE.bazel.bak"

# Verify the patch was applied successfully.
# Check only within the module() block to avoid false-passing on
# bazel_dep versions that happen to match the tag version.
if ! sed -n '/^module(/,/^)/p' "${TMPDIR}/${PREFIX}/MODULE.bazel" | grep -q "version = \"${VERSION}\""; then
echo "::error::Failed to patch MODULE.bazel version to ${VERSION}."
echo "::error::Check that MODULE.bazel has a module() block with a version field."
exit 1
fi

# Re-create the archive from the patched tree with deterministic metadata.
# GNU tar (used in CI on ubuntu-latest) supports --sort, --mtime, --owner, --group.
# On macOS, bsdtar does not support these flags; fall back to basic tar.
TAR_REPRO_OPTS=""
if tar --help 2>&1 | grep -q -- '--sort='; then
TAR_REPRO_OPTS="--sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner"
fi
# gzip -n strips the gzip timestamp for reproducible output (portable).
tar -cf - -C "${TMPDIR}" ${TAR_REPRO_OPTS} "${PREFIX}" | gzip -n > "${ARCHIVE}"

echo "Archive created: $(ls -lh "${ARCHIVE}" | awk '{print $5}')"

# Compute integrity hash (SHA256 in SRI format for MODULE.bazel snippets)
SHA256=$(shasum -a 256 "${ARCHIVE}" | cut -d' ' -f1)
SRI="sha256-$(echo -n "${SHA256}" | xxd -r -p | base64)"

Expand Down
Loading