blob: ad4af2cd5d60848eb9b6b2723b71b73a3c375b1f [file]
#!/usr/bin/env bash
# release.sh — Manual "prepare + perform" for Apache Velocity Engine
#
# Usage:
# ./release.sh <RELEASE_VERSION> <RC_NUMBER> [--dry-run] [--gpg-keyname <KEYID>] [--profile <MVN_PROFILE>] [--help]
#
# Example:
# ./release.sh 2.4 RC1
#
# Behavior:
# - Sets versions to RELEASE (drops -SNAPSHOT), sets <scm><tag> to RELEASE TAG (e.g., 2.5),
# commits, creates and pushes RC tag (e.g., 2.5-RC1), then runs `mvn release:perform`
# from that tag with goals=deploy (tests skipped), using SCM URL read from the POM.
# - After perform, assembles a "dist/dev" staging folder under:
# target/velocity-staging/engine/${RELEASE}/
# computes *.sha256 for each file and signature, and prints copy instructions.
# - Bumps to NEXT dev version (defaults to next MINOR: X.(Y+1)-SNAPSHOT),
# resets <scm><tag> to HEAD, commits.
#
# Requirements:
# - Credentials & server ids in ~/.m2/settings.xml
# <server>
# <id>apache.releases.https</id>
# <username>your-apache-id</username>
# <password>your-apache-password</password>
# </server>
#
#
# The GPG key can be provided:
# - by defining an "apache-release" profile in ~/.m2/settings.xml :
# <profile>
# <id>apache-release-key</id>
# <properties>
# <gpg.keyname>...</gpg.keyname>
# </properties>
# </profile>
# - by using another profile name with the --profile option
# - by explicitly giving a GPG key with the --gpg-keyname option
set -euo pipefail
# -------------------------
# Defaults / globals
# -------------------------
MODULE=engine
DRY_RUN=0
MVN_PROFILE="${MVN_PROFILE:-apache-release}"
GPG_KEYNAME="${GPG_KEYNAME:-}"
# -------------------------
# Helpers
# -------------------------
die() { echo "ERROR: $*" >&2; exit 1; }
pause() { read -r -p "$* Press Enter to continue..."; }
is_dry() { [[ "$DRY_RUN" -eq 1 ]]; }
run() { if is_dry; then echo "[DRY-RUN] $*"; else echo "[RUN] $*"; eval "$@"; fi }
git_run() { if is_dry; then echo "[DRY-RUN] git $*"; else echo "[RUN] git $*"; git "$@"; fi }
mvn_q() { if is_dry; then echo "[DRY-RUN] mvn $*"; else echo "[RUN] mvn $*"; mvn "$@"; fi }
usage() {
cat <<EOF
Usage:
$0 <RELEASE_VERSION> <RC_NUMBER> [--dry-run] [--gpg-keyname <KEYID>] [--profile <MVN_PROFILE>] [--help]
Examples:
$0 2.4 RC1
$0 2.4.1 RC2 --gpg-keyname BEFEEF227A98B809
Options:
--dry-run Show what would happen; do not mutate git or run deploys.
--gpg-keyname K Explicit GPG key id/name (overrides settings.xml).
--profile P Maven profile to activate for release (default: apache-release).
--help Print this help.
EOF
}
# -------------------------
# Parse args
# -------------------------
if [[ "${1:-}" == "--help" || $# -lt 2 ]]; then usage; exit 0; fi
RELEASE="$1"; shift
RC_NUM_RAW="$1"; shift
# Normalize RC: enforce UPPERCASE "RCN"
RC_NUM="$(echo "$RC_NUM_RAW" | tr '[:lower:]' '[:upper:]')"
[[ "$RC_NUM" =~ ^RC[0-9]+$ ]] || die "RC number must look like RC1, RC2... (got '$RC_NUM_RAW')"
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1 ;;
--gpg-keyname) shift; GPG_KEYNAME="${1:-}"; [[ -n "$GPG_KEYNAME" ]] || die "--gpg-keyname requires a value" ;;
--profile) shift; MVN_PROFILE="${1:-}"; [[ -n "$MVN_PROFILE" ]] || die "--profile requires a value" ;;
--help) usage; exit 0 ;;
*) die "Unknown option: $1" ;;
esac
shift
done
# Derived names
RELEASE_TAG="$RELEASE" # final tag (e.g., 2.4)
RC_TAG="${RELEASE}-${RC_NUM}" # e.g., 2.4-RC1
# Compute NEXT version = next minor snapshot by default (2.4 -> 2.5-SNAPSHOT)
compute_next_version() {
local v="$1"
if [[ "$v" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))?$ ]]; then
local MAJ="${BASH_REMATCH[1]}"; local MIN="${BASH_REMATCH[2]}"
echo "${MAJ}.$((MIN+1))-SNAPSHOT"
else
# Fallback: append -SNAPSHOT if not three-part semantic
echo "${v}-SNAPSHOT"
fi
}
NEXT_DEFAULT="$(compute_next_version "$RELEASE")"
echo "Planned versions/tags:"
echo " RELEASE : $RELEASE"
echo " RC TAG : $RC_TAG"
echo " FINAL TAG : $RELEASE_TAG"
echo " NEXT (default): $NEXT_DEFAULT"
pause "Confirm the above."
# -------------------------
# Pre-flight checks
# -------------------------
git_run fetch --all --tags
# We don't enforce being on 'main'; use current branch.
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
[[ -n "$CURRENT_BRANCH" ]] || die "Cannot determine current branch."
[[ -z "$(git status --porcelain)" ]] || die "Working tree is not clean."
# Quick build to ensure green state
mvn_q -q -DskipTests -U verify
# Verify no SNAPSHOT deps (Enforcer rule)
# (You can also wire requireReleaseDeps in a profile; here we invoke ad-hoc)
mvn_q -Denforcer.skip=false -Drules=requireReleaseDeps enforcer:enforce
# Determine SCM URLs from POM
SCM_DEV_URL="$(mvn -q help:evaluate -Dexpression=project.scm.developerConnection -DforceStdout || true)"
if [[ -z "$SCM_DEV_URL" || "$SCM_DEV_URL" == "null" ]]; then
SCM_DEV_URL="$(mvn -q help:evaluate -Dexpression=project.scm.connection -DforceStdout || true)"
fi
[[ -n "$SCM_DEV_URL" && "$SCM_DEV_URL" != "null" ]] || die "Could not read project.scm.[developer]Connection from POM."
# -------------------------
# Step 1: Set release version and <scm><tag> to final tag; commit
# -------------------------
pause "About to set project version to $RELEASE and <scm><tag> to $RELEASE_TAG."
mvn_q -q versions:set -DnewVersion="$RELEASE"
mvn_q -q versions:commit
# Stamp <scm><tag> with the final release tag for the release commit
mvn_q -q versions:set-scm-tag -DnewTag="$RELEASE_TAG"
git_run add -A
git_run commit -m "Release $RELEASE: set versions to $RELEASE and <scm><tag>=$RELEASE_TAG"
# -------------------------
# Step 2: Create & push RC tag (must be pushed before perform)
# -------------------------
pause "About to create and push RC tag $RC_TAG at current commit."
git_run tag -a "$RC_TAG" -m "Velocity $MODULE $RELEASE (RC: $RC_NUM)"
git_run push origin "refs/tags/$RC_TAG:refs/tags/$RC_TAG"
# -------------------------
# Step 3: Perform (deploy) from the RC tag
# -------------------------
# Build arguments for perform
MVN_ARGS="-Dgpg.sign=true -DskipTests"
[[ -n "$GPG_KEYNAME" ]] && MVN_ARGS="$MVN_ARGS -Dgpg.keyname=$GPG_KEYNAME"
[[ -n "$MVN_PROFILE" ]] && MVN_ARGS="$MVN_ARGS -P$MVN_PROFILE"
pause "About to run mvn release:perform from tag $RC_TAG (goals=deploy)."
mvn_q -B -e \
-DconnectionUrl="$SCM_DEV_URL" \
-Dtag="$RC_TAG" \
-Dgoals="deploy" \
-Darguments="$MVN_ARGS" \
release:perform
# -------------------------
# Step 4: Assemble dist/dev staging folder + sha256 (SMART COPY)
# -------------------------
STAGE_DIR="target/velocity-staging/${MODULE}/${RELEASE}"
run mkdir -p "$STAGE_DIR"
# We'll scan:
# 1) the current project (root) target
# 2) submodules' targets (after release:perform, they exist under target/checkout/**/target)
# 3) include release-notes.html if present at repo root
#
# We flatten everything into $STAGE_DIR (like your dist/dev listing).
# We keep only relevant files for the release version and common packaging.
#
copy_matches() {
local search_root="$1"
[[ -d "$search_root" ]] || return 0
# Find candidate files containing the release version in the name,
# and matching common release extensions (pom, jar, sources, javadoc, zips, asc).
# Skip Maven metadata, checksum files we will re-create, and transient files.
while IFS= read -r -d '' f; do
run cp -v "$f" "$STAGE_DIR/"
done < <(find "$search_root" -type f -print0 \
| grep -zE "/target/|/dist/|/assembly/|/package/" \
| grep -zE "${RELEASE//./\\.}" \
| grep -zE "\.(pom|jar|zip|tar\.gz|asc)$" \
| grep -zEv "\.sha(1|256)$" )
}
# 1) root target
copy_matches "target"
# 2) submodules under the perform checkout (present after release:perform)
copy_matches "target/checkout"
# also scan submodules of the current workspace (useful when running without perform)
copy_matches "."
# 3) include release-notes.html if present
if [[ -f "release-notes.html" ]]; then
run cp -v "release-notes.html" "$STAGE_DIR/"
fi
# Compute SHA-256 for every file in STAGE_DIR except existing .sha256 files.
# (This yields checksums for artifacts AND for their .asc signatures.)
if [[ -d "$STAGE_DIR" ]]; then
(
cd "$STAGE_DIR"
for fileitem in *; do
[[ -f "$fileitem" ]] || continue
[[ "$fileitem" =~ \.sha256$ ]] && continue
run "sha256sum \"$fileitem\" > \"$fileitem.sha256\""
done
)
fi
echo
echo "===================================================================="
echo "Dist/dev staging assembled in:"
echo " $STAGE_DIR"
echo
echo "Now copy the contents of that directory to your local checkout of:"
echo " https://dist.apache.org/repos/dist/dev/velocity/${MODULE}/${RELEASE}/"
echo "Then: svn add/commit there."
echo "===================================================================="
echo
# -------------------------
# Step 5: Generate vote email skeleton
# -------------------------
VOTE_FILE="target/${MODULE}-${RELEASE}-rc-vote.txt"
STAGING_TODO_URL="(paste Nexus staging repo URL here after deploy)"
cat > "$VOTE_FILE" <<EOF
Subject: [VOTE] Release Apache Velocity ${MODULE^} $RELEASE ($RC_TAG)
Hi all,
This is a vote to release Apache Velocity ${MODULE^} $RELEASE, RC: $RC_TAG.
Artifacts have been staged in Nexus:
$STAGING_TODO_URL
The dist/dev artifacts to verify (source & binary) are here (after you copy them):
https://dist.apache.org/repos/dist/dev/velocity/${MODULE}/${RELEASE}/
Please vote on the release:
[ ] +1 Release the artifacts as Apache Velocity ${MODULE^} $RELEASE
[ ] 0 No opinion
[ ] -1 Do not release (please state why)
Vote will be open for at least 72 hours.
Checklist:
- [ ] Check signatures (*.asc) and hashes (*.sha256)
- [ ] Build from source
- [ ] Verify LICENSE/NOTICE
- [ ] Sanity check artifacts (pom, javadoc, sources, binaries)
- [ ] Compare tag $RC_TAG against sources
Tag to verify:
$RC_TAG
Thanks,
(Your Name)
EOF
echo "Vote email skeleton generated at: $VOTE_FILE"
# -------------------------
# Step 6: Bump to NEXT dev version and reset <scm><tag>=HEAD; commit
# -------------------------
read -r -p "Enter NEXT development version [default: $NEXT_DEFAULT]: " NEXT_IN
NEXT="${NEXT_IN:-$NEXT_DEFAULT}"
pause "About to bump to $NEXT and reset <scm><tag>=HEAD."
mvn_q -q versions:set -DnewVersion="$NEXT"
mvn_q -q versions:commit
mvn_q -q versions:set-scm-tag -DnewTag=HEAD
git_run add -A
git_run commit -m "Start $NEXT (reset <scm><tag>=HEAD)"
# Push branch changes (optional; uncomment to push automatically)
git_run push origin "$CURRENT_BRANCH" || true
echo "Done."