blob: 32607c8602e8411c48105b7cf6f54f8c49aa231d [file]
#!/bin/bash
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIBS_DIR="${SCRIPT_DIR}/../libs"
source "${LIBS_DIR}/_constants.sh"
source "${LIBS_DIR}/_log.sh"
source "${LIBS_DIR}/_exec.sh"
source "${LIBS_DIR}/_version.sh"
source "${LIBS_DIR}/_nexus.sh"
source "${LIBS_DIR}/_svn.sh"
# ---------------------------------------------------------------------------
# Usage
# ---------------------------------------------------------------------------
function usage {
cat <<EOF
Usage: $0 <version> <staging-repo-id> [--rc <num>] [--allow-description-mismatch]
Publish a release after the vote passes.
Arguments:
version Release version (e.g., 1.18.0)
staging-repo-id Nexus staging repository ID (e.g., orgapacheparquet-1234)
Options:
--rc <num> RC number that passed the vote (default: auto-detect latest)
--allow-description-mismatch
Bypass the staging-repo description check (for recovery scenarios)
--help Show this help
Before any destructive action, this script verifies that the staging repo
belongs to org.apache.parquet, is in 'closed' state, contains
${NEXUS_VERIFY_ARTIFACT_ID:-parquet-common}-<version>.pom, and has a
description matching "Apache Parquet <version> RC<num>".
The next development version is auto-computed by incrementing the patch
version (e.g., 1.18.0 -> 1.18.1-SNAPSHOT).
Environment variables:
DRY_RUN Set to 0 for real execution (default: 1)
NEXUS_USERNAME Apache Nexus username
NEXUS_PASSWORD Apache Nexus password
SVN_USERNAME SVN username for dist.apache.org
SVN_PASSWORD SVN password
GITHUB_TOKEN GitHub token for release creation
Example:
DRY_RUN=1 $0 1.18.0 orgapacheparquet-1234
DRY_RUN=1 $0 1.18.0 orgapacheparquet-1234 --rc 2
EOF
exit "${1:-0}"
}
# ---------------------------------------------------------------------------
# Parse arguments
# ---------------------------------------------------------------------------
version=""
staging_repo_id=""
rc_num=""
allow_description_mismatch=0
positional=()
while [[ $# -gt 0 ]]; do
case "$1" in
--rc)
if [[ -z "${2:-}" ]]; then
print_error "--rc requires a value"
usage 1
fi
rc_num="$2"
shift 2
;;
--allow-description-mismatch)
allow_description_mismatch=1
shift
;;
--help|-h)
usage 0
;;
-*)
print_error "Unknown option: $1"
usage 1
;;
*)
positional+=("$1")
shift
;;
esac
done
if [[ ${#positional[@]} -lt 2 ]]; then
print_error "Expected 2 positional arguments (version, staging-repo-id), got ${#positional[@]}"
usage 1
fi
version="${positional[0]}"
staging_repo_id="${positional[1]}"
# ---------------------------------------------------------------------------
# Validate inputs
# ---------------------------------------------------------------------------
step_summary "## Release Publication"
step_summary ""
if [[ ${DRY_RUN:-1} -eq 1 ]]; then
step_summary "> **DRY RUN** -- no changes will be made"
step_summary ""
fi
if ! validate_and_extract_version "${version}"; then
print_error "Invalid version format: '${version}'"
exit 1
fi
next_dev_version="${major}.${minor}.$(( patch + 1 ))"
if ! [[ "${staging_repo_id}" =~ ^[a-zA-Z][a-zA-Z0-9._-]*$ ]]; then
print_error "Invalid staging repository ID: '${staging_repo_id}'. Expected alphanumeric with dots/hyphens (e.g., orgapacheparquet-1234)."
exit 1
fi
if [[ -z "${rc_num}" ]]; then
print_info "No RC number specified, auto-detecting latest RC for ${version}..."
if ! find_latest_rc_number "${version}"; then
exit 1
fi
rc_num="${latest_rc_number}"
print_info "Auto-detected latest RC: rc${rc_num}"
else
if ! [[ "${rc_num}" =~ ^[0-9]+$ ]]; then
print_error "Invalid RC number: '${rc_num}'. Expected a non-negative integer."
exit 1
fi
if find_latest_rc_number "${version}" 2>/dev/null; then
if [[ "${rc_num}" -ne "${latest_rc_number}" ]]; then
print_error "RC${rc_num} is not the latest RC for ${version}. Latest is rc${latest_rc_number}."
print_error "Publishing an older RC is likely a mistake. If intentional, delete the newer RC tags first."
exit 1
fi
fi
fi
rc_tag="${TAG_PREFIX}${version}-rc${rc_num}"
final_tag="${TAG_PREFIX}${version}"
if ! git rev-parse "${rc_tag}" >/dev/null 2>&1; then
print_error "RC tag ${rc_tag} does not exist"
exit 1
fi
rc_commit=$(git rev-list -1 "${rc_tag}")
current_commit=$(git rev-parse HEAD)
if [[ "${current_commit}" != "${rc_commit}" ]]; then
print_error "Current HEAD (${current_commit}) does not match RC tag ${rc_tag} (${rc_commit})"
print_error "The release branch has commits beyond the voted RC. Either reset the branch or create a new RC."
exit 1
fi
if git rev-parse "${final_tag}" >/dev/null 2>&1; then
print_error "Final release tag ${final_tag} already exists"
exit 1
fi
step_summary "| Parameter | Value |"
step_summary "| --- | --- |"
step_summary "| Version | \`${version}\` |"
step_summary "| RC tag | \`${rc_tag}\` |"
step_summary "| Final tag | \`${final_tag}\` |"
step_summary "| Staging repo | \`${staging_repo_id}\` |"
step_summary "| Next dev version | \`${next_dev_version}-SNAPSHOT\` |"
step_summary "| Commit | \`${rc_commit}\` |"
# ---------------------------------------------------------------------------
# Step 0: Verify staging repository before any destructive action
# ---------------------------------------------------------------------------
step_summary ""
step_summary "### Staging Repository Verification"
if ! nexus_verify_staging_repo "${staging_repo_id}" "${version}" "${rc_num}" "${allow_description_mismatch}"; then
step_summary "Staging repository verification: **FAILED**"
exit 1
fi
step_summary "Staging repository \`${staging_repo_id}\` verified"
# ---------------------------------------------------------------------------
# Step 1: Move SVN artifacts from dist/dev to dist/release
# ---------------------------------------------------------------------------
step_summary ""
step_summary "### SVN Promotion"
svn_promote_rc_to_release "${version}" "${rc_num}"
step_summary "Promoted \`${rc_tag}\` -> \`${final_tag}\` on dist.apache.org"
# ---------------------------------------------------------------------------
# Step 2: Clean up superseded patch releases on the same minor branch
# ---------------------------------------------------------------------------
# Policy: only patch releases of the *same* minor (parquet-${major}.${minor}.x)
# that are older than ${version} are removed from dist/release/parquet/.
# Releases on other minor branches are left in place and must be retired
# manually. See svn_list_old_releases for the full rationale.
step_summary ""
step_summary "### Patch Release Cleanup (parquet-${major}.${minor}.x)"
if [[ ${DRY_RUN:-1} -ne 1 ]]; then
if ! old_versions=$(svn_list_old_releases "${version}"); then
exit 1
fi
if [[ -n "${old_versions}" ]]; then
step_summary "Removing superseded patches on parquet-${major}.${minor}.x:"
while IFS= read -r old_dir; do
[[ -z "${old_dir}" ]] && continue
svn_remove_release "${old_dir}" "${version}"
step_summary "- Removed \`${old_dir}\`"
done <<< "${old_versions}"
else
step_summary "No superseded ${major}.${minor}.x patches to clean up"
fi
else
print_command "Dry-run, WOULD remove superseded ${major}.${minor}.x patches from dist/release"
step_summary "Would remove superseded ${major}.${minor}.x patches (dry-run)"
fi
# ---------------------------------------------------------------------------
# Step 3: Create final release tag
# ---------------------------------------------------------------------------
step_summary ""
step_summary "### Release Tag"
exec_process git tag -a "${final_tag}" "${rc_commit}" -m "Release Apache Parquet ${version}"
exec_process git push origin "${final_tag}"
step_summary "Created tag \`${final_tag}\` at \`${rc_commit}\`"
# ---------------------------------------------------------------------------
# Step 4: Release Nexus staging repo
# ---------------------------------------------------------------------------
step_summary ""
step_summary "### Nexus Release"
nexus_release_staging_repo "${staging_repo_id}" "Apache Parquet ${version}"
step_summary "Released staging repository \`${staging_repo_id}\` to Maven Central"
# ---------------------------------------------------------------------------
# Step 5: Create GitHub Release
# ---------------------------------------------------------------------------
step_summary ""
step_summary "### GitHub Release"
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
# If a pre-release exists for the RC tag, update it; otherwise create a new release
if gh release view "${rc_tag}" &>/dev/null 2>&1; then
print_info "Found existing pre-release for ${rc_tag}"
fi
exec_process gh release create "${final_tag}" \
--title "Apache Parquet ${version}" \
--generate-notes \
--latest \
--target "${rc_commit}"
step_summary "Created GitHub release for \`${final_tag}\`"
else
print_warning "GITHUB_TOKEN not set, skipping GitHub release creation"
step_summary "Skipped GitHub release (no GITHUB_TOKEN)"
fi
# ---------------------------------------------------------------------------
# Step 6: Bump to next development version
# ---------------------------------------------------------------------------
step_summary ""
step_summary "### Version Bump"
next_snapshot="${next_dev_version}-SNAPSHOT"
print_info "Bumping version to ${next_snapshot}..."
set_pom_version "${next_snapshot}"
# Update previous.version property
exec_process ./mvnw -pl . versions:set-property \
-Dproperty=previous.version -DnewVersion="${version}" \
--batch-mode -q
exec_process git add -A
exec_process git commit -m "Prepare for next development iteration (${next_snapshot})"
# Refuse to push the version bump to anything that doesn't look like a
# release branch. Otherwise a workflow run dispatched against master
# (e.g. after `prepare-rc.sh --skip-branch-creation`) would push the
# next-dev-version commit straight to master.
current_branch=$(git rev-parse --abbrev-ref HEAD)
expected_branch="${BRANCH_PREFIX}${major}.${minor}.x"
if [[ "${current_branch}" != "${expected_branch}" ]]; then
print_error "Refusing to push version bump: current branch is '${current_branch}'"
print_error "Expected to be on release branch '${expected_branch}' for version ${version}."
print_error "Run the publish workflow with that branch selected, or check out '${expected_branch}' locally."
exit 1
fi
# Also confirm the format independently in case someone has named a
# non-release branch to look like one.
if ! validate_and_extract_branch_version "${current_branch}"; then
print_error "Branch '${current_branch}' is not a valid release branch name."
exit 1
fi
exec_process git push origin HEAD
step_summary "Bumped version to \`${next_snapshot}\` on branch \`${current_branch}\`, set \`previous.version=${version}\`"
# ---------------------------------------------------------------------------
# Step 7: Generate announce email
# ---------------------------------------------------------------------------
step_summary ""
step_summary "### Announce Email"
step_summary ""
step_summary '```'
step_summary "Subject: [ANNOUNCE] Apache Parquet ${version}"
step_summary ""
step_summary "I'm pleased to announce the release of Apache Parquet ${version}!"
step_summary ""
step_summary "Parquet is a general-purpose columnar file format for nested data. It uses"
step_summary "space-efficient encodings and a compressed and splittable structure for"
step_summary "processing frameworks like Hadoop."
step_summary ""
step_summary "Changes are listed at: https://github.com/apache/parquet-java/releases/tag/${final_tag}"
step_summary ""
step_summary "This release can be downloaded from: https://parquet.apache.org/downloads/"
step_summary ""
step_summary "Java artifacts are available from Maven Central."
step_summary ""
step_summary "Thanks to everyone for contributing!"
step_summary '```'
# ---------------------------------------------------------------------------
# Step 8: Reminder -- update parquet.apache.org
# ---------------------------------------------------------------------------
release_date=$(date +%Y-%m-%d)
step_summary ""
step_summary "### Manual Follow-up: Update parquet.apache.org"
step_summary ""
step_summary "Create a release blog post PR against \`apache/parquet-site\`."
step_summary "Add a new file \`content/en/blog/parquet-java/parquet-java-${version}.md\` with:"
step_summary ""
step_summary '```markdown'
step_summary "---"
step_summary "title: \"Apache Parquet Java ${version}\""
step_summary "date: ${release_date}"
step_summary "summary: \"Release notes for Apache Parquet Java ${version}\""
step_summary "---"
step_summary ""
step_summary "Apache Parquet Java ${version} has been released."
step_summary ""
step_summary "For the full list of changes, see the"
step_summary "[release notes](https://github.com/apache/parquet-java/releases/tag/${final_tag})."
step_summary ""
step_summary "Java artifacts are available from"
step_summary "[Maven Central](https://search.maven.org/search?q=g:org.apache.parquet%20AND%20v:${version})."
step_summary ""
step_summary "Source and binary downloads are available from the"
step_summary "[Apache downloads page](https://parquet.apache.org/downloads/)."
step_summary '```'
step_summary ""
step_summary "Submit the PR against the \`staging\` branch of"
step_summary "[\`apache/parquet-site\`](https://github.com/apache/parquet-site)."
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
step_summary ""
step_summary "---"
step_summary "### Summary"
step_summary ""
step_summary "| Step | Status |"
step_summary "| --- | --- |"
step_summary "| SVN promotion | done |"
step_summary "| Old release cleanup | done |"
step_summary "| Final release tag | \`${final_tag}\` |"
step_summary "| Nexus release | \`${staging_repo_id}\` released |"
step_summary "| GitHub release | created |"
step_summary "| Version bump | \`${next_snapshot}\` |"
step_summary "| Announce email | generated |"
step_summary "| Site update | **manual** -- see template above |"
print_success "Release ${version} published successfully!"