blob: c3081a8f7750a22b77926c44b1c003afe2de83d7 [file]
#!/usr/bin/env 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)
SOURCE_ROOT=$(cd "${SCRIPT_DIR}/../.." && pwd)
VERSION=""
RC=""
SIGNING_KEY=""
REMOTE="origin"
DIST_DEV_BASE_URL="https://dist.apache.org/repos/dist/dev/paimon"
DIST_RELEASE_BASE_URL="https://dist.apache.org/repos/dist/release/paimon"
CONFIRM_VOTE_PASSED=false
SKIP_GITHUB_RELEASE=false
DRY_RUN=false
usage() {
cat <<'EOF'
Publish an approved Apache Paimon C++ release candidate.
Usage:
publish_release.sh --version VERSION --rc RC --signing-key KEY_ID \
--confirm-vote-passed [options]
Required:
--version VERSION Approved release version
--rc RC Approved release candidate number
--signing-key KEY_ID OpenPGP key used to sign the final Git tag
--confirm-vote-passed Explicitly confirm that the PMC vote passed
Options:
--remote REMOTE Apache Git remote (default: origin)
--dist-dev-base URL ASF dist/dev project URL
--dist-release-base URL ASF dist/release project URL
--skip-github-release Do not create the GitHub Release
--dry-run Print the planned publication identifiers and exit
-h, --help Show this help
The script is resumable if the final tag, SVN move, or GitHub Release already
completed and still points to the approved RC commit.
EOF
}
fail() {
echo "Error: $*" >&2
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
}
validate_release_directory() {
local directory=$1
local -a entries
local entry
local name
shopt -s dotglob nullglob
entries=("${directory}"/*)
shopt -u dotglob nullglob
[[ ${#entries[@]} -eq 3 ]] ||
fail "${directory} must contain exactly the archive, signature, and checksum"
for entry in "${entries[@]}"; do
[[ -f "${entry}" ]] || fail "release contains a non-file entry: ${entry}"
name=$(basename "${entry}")
case "${name}" in
"${ARTIFACT_NAME}" | \
"${ARTIFACT_NAME}.asc" | \
"${ARTIFACT_NAME}.sha512")
;;
*)
fail "release contains an unexpected file: ${name}"
;;
esac
done
}
while [[ $# -gt 0 ]]; do
case "$1" in
--version)
[[ $# -ge 2 ]] || fail "--version requires a value"
VERSION=$2
shift 2
;;
--rc)
[[ $# -ge 2 ]] || fail "--rc requires a value"
RC=$2
shift 2
;;
--signing-key)
[[ $# -ge 2 ]] || fail "--signing-key requires a value"
SIGNING_KEY=$2
shift 2
;;
--remote)
[[ $# -ge 2 ]] || fail "--remote requires a value"
REMOTE=$2
shift 2
;;
--dist-dev-base)
[[ $# -ge 2 ]] || fail "--dist-dev-base requires a value"
DIST_DEV_BASE_URL=${2%/}
shift 2
;;
--dist-release-base)
[[ $# -ge 2 ]] || fail "--dist-release-base requires a value"
DIST_RELEASE_BASE_URL=${2%/}
shift 2
;;
--confirm-vote-passed)
CONFIRM_VOTE_PASSED=true
shift
;;
--skip-github-release)
SKIP_GITHUB_RELEASE=true
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
fail "unknown argument: $1"
;;
esac
done
[[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
fail "--version must use MAJOR.MINOR.PATCH format"
[[ "${RC}" =~ ^[0-9]+$ ]] || fail "--rc must be a non-negative integer"
[[ -n "${SIGNING_KEY}" ]] || fail "--signing-key is required"
[[ "${CONFIRM_VOTE_PASSED}" == true ]] ||
fail "--confirm-vote-passed is required"
RC_TAG="v${VERSION}-rc${RC}"
RELEASE_TAG="v${VERSION}"
RC_ID="paimon-cpp-${VERSION}-rc${RC}"
RELEASE_ID="paimon-cpp-${VERSION}"
ARTIFACT_NAME="apache-paimon-cpp-${VERSION}-src.tgz"
RC_URL="${DIST_DEV_BASE_URL}/${RC_ID}"
RELEASE_URL="${DIST_RELEASE_BASE_URL}/${RELEASE_ID}"
if [[ "${DRY_RUN}" == true ]]; then
cat <<EOF
Approved RC tag: ${RC_TAG}
Final release tag: ${RELEASE_TAG}
ASF source: ${RC_URL}
ASF destination: ${RELEASE_URL}
Git remote: ${REMOTE}
Create GitHub Release: $([[ "${SKIP_GITHUB_RELEASE}" == true ]] && echo no || echo yes)
EOF
exit 0
fi
for command in git gpg svn; do
require_command "${command}"
done
if [[ "${SKIP_GITHUB_RELEASE}" == false ]]; then
require_command gh
fi
cd "${SOURCE_ROOT}"
[[ -z "$(git status --porcelain)" ]] ||
fail "working tree must be clean before publishing a release"
REMOTE_URL=$(git remote get-url "${REMOTE}" 2>/dev/null) ||
fail "Git remote does not exist: ${REMOTE}"
case "${REMOTE_URL}" in
git@github.com:apache/paimon-cpp.git | \
https://github.com/apache/paimon-cpp | \
https://github.com/apache/paimon-cpp.git | \
ssh://git@github.com/apache/paimon-cpp.git)
;;
*)
fail "${REMOTE} must point to apache/paimon-cpp, found ${REMOTE_URL}"
;;
esac
git rev-parse --verify "${RC_TAG}^{tag}" >/dev/null 2>&1 ||
fail "signed RC tag is missing: ${RC_TAG}"
git verify-tag "${RC_TAG}"
RC_COMMIT=$(git rev-parse "${RC_TAG}^{commit}")
RC_PRESENT=false
RELEASE_PRESENT=false
if svn info "${RC_URL}" >/dev/null 2>&1; then
RC_PRESENT=true
fi
if svn info "${RELEASE_URL}" >/dev/null 2>&1; then
RELEASE_PRESENT=true
fi
if [[ "${RC_PRESENT}" == true && "${RELEASE_PRESENT}" == true ]]; then
fail "both RC and final release directories exist; resolve SVN state manually"
fi
if [[ "${RC_PRESENT}" == false && "${RELEASE_PRESENT}" == false ]]; then
fail "approved artifacts are missing from both dist/dev and dist/release"
fi
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "${TEMP_DIR}"' EXIT
RELEASE_FILES_DIR="${TEMP_DIR}/${RELEASE_ID}"
if [[ "${RC_PRESENT}" == true ]]; then
svn export --quiet "${RC_URL}" "${RELEASE_FILES_DIR}"
else
svn export --quiet "${RELEASE_URL}" "${RELEASE_FILES_DIR}"
fi
validate_release_directory "${RELEASE_FILES_DIR}"
for suffix in "" ".asc" ".sha512"; do
[[ -f "${RELEASE_FILES_DIR}/${ARTIFACT_NAME}${suffix}" ]] ||
fail "approved release is missing ${ARTIFACT_NAME}${suffix}"
done
"${SCRIPT_DIR}/verify_release_candidate.sh" \
--keys-url "https://downloads.apache.org/paimon/KEYS" \
--git-ref "${RC_TAG}" \
--skip-build \
"${RELEASE_FILES_DIR}/${ARTIFACT_NAME}"
if git rev-parse --verify "${RELEASE_TAG}^{tag}" >/dev/null 2>&1; then
RELEASE_COMMIT=$(git rev-parse "${RELEASE_TAG}^{commit}")
[[ "${RELEASE_COMMIT}" == "${RC_COMMIT}" ]] ||
fail "${RELEASE_TAG} does not point to the approved RC commit"
git verify-tag "${RELEASE_TAG}"
echo "Reusing verified final tag ${RELEASE_TAG}."
else
git tag -s -u "${SIGNING_KEY}" \
-m "Release Apache Paimon C++ ${VERSION}" \
"${RELEASE_TAG}" "${RC_COMMIT}"
git verify-tag "${RELEASE_TAG}"
fi
git push "${REMOTE}" "${RELEASE_TAG}"
if [[ "${RELEASE_PRESENT}" == true ]]; then
echo "Release artifacts are already present at ${RELEASE_URL}."
else
svn mv "${RC_URL}" "${RELEASE_URL}" \
-m "Release Apache Paimon C++ ${VERSION}"
fi
if [[ "${SKIP_GITHUB_RELEASE}" == false ]]; then
if gh release view "${RELEASE_TAG}" --repo apache/paimon-cpp >/dev/null 2>&1; then
echo "Checking existing GitHub Release ${RELEASE_TAG}."
EXISTING_ASSETS_DIR="${TEMP_DIR}/existing-assets"
mkdir "${EXISTING_ASSETS_DIR}"
for suffix in "" ".asc" ".sha512"; do
ASSET_NAME="${ARTIFACT_NAME}${suffix}"
if gh release view "${RELEASE_TAG}" \
--repo apache/paimon-cpp \
--json assets \
--jq '.assets[].name' |
grep -Fxq "${ASSET_NAME}"; then
gh release download "${RELEASE_TAG}" \
--repo apache/paimon-cpp \
--dir "${EXISTING_ASSETS_DIR}" \
--pattern "${ASSET_NAME}"
cmp "${EXISTING_ASSETS_DIR}/${ASSET_NAME}" \
"${RELEASE_FILES_DIR}/${ASSET_NAME}" >/dev/null ||
fail "GitHub Release asset differs from ASF release: ${ASSET_NAME}"
else
gh release upload "${RELEASE_TAG}" \
--repo apache/paimon-cpp \
"${RELEASE_FILES_DIR}/${ASSET_NAME}"
fi
done
else
gh release create "${RELEASE_TAG}" \
--repo apache/paimon-cpp \
--verify-tag \
--generate-notes \
--title "Apache Paimon C++ ${VERSION}" \
"${RELEASE_FILES_DIR}/${ARTIFACT_NAME}" \
"${RELEASE_FILES_DIR}/${ARTIFACT_NAME}.asc" \
"${RELEASE_FILES_DIR}/${ARTIFACT_NAME}.sha512"
fi
fi
cat <<EOF
Apache Paimon C++ ${VERSION} has been published.
Next steps:
1. Verify the release appears under https://downloads.apache.org/paimon/.
2. Wait at least 24 hours after publication before updating public download
or documentation pages and announcing the release.
3. Remove superseded Paimon C++ releases from dist/release after confirming
they remain available from archive.apache.org.
4. Update the project download and documentation pages.
After completing the 24-hour wait, send the following announcement in plain
text from an apache.org email address.
To: dev@paimon.apache.org
CC: announce@apache.org
Subject: [ANNOUNCE][C++] Apache Paimon C++ ${VERSION} released
The Apache Paimon community is pleased to announce the release of
Apache Paimon C++ ${VERSION}.
Source release:
https://downloads.apache.org/paimon/${RELEASE_ID}/
Release notes:
https://github.com/apache/paimon-cpp/releases/tag/${RELEASE_TAG}
Thanks to everyone who contributed to this release.
EOF