| #!/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) | |
| RELEASE_VERSION="" | |
| GIT_REF="" | |
| OUTPUT_DIR="${SOURCE_ROOT}/release" | |
| SIGNING_KEY="" | |
| usage() { | |
| cat <<'EOF' | |
| Create an Apache Paimon C++ source release from an immutable Git ref. | |
| Usage: | |
| create_source_release.sh --version VERSION --git-ref REF [options] | |
| Required: | |
| --version VERSION Release version, for example 0.3.0 | |
| --git-ref REF Commit or signed RC tag to archive | |
| Options: | |
| --output-dir DIR Output directory (default: <repository>/release) | |
| --signing-key KEY_ID Create an ASCII-armored detached OpenPGP signature | |
| -h, --help Show this help | |
| The script creates: | |
| apache-paimon-cpp-VERSION-src.tgz | |
| apache-paimon-cpp-VERSION-src.tgz.sha512 | |
| apache-paimon-cpp-VERSION-src.tgz.asc (when --signing-key is provided) | |
| Existing artifacts are never overwritten. | |
| GNU gzip is required so macOS and Linux produce the same compressed bytes. | |
| Set PAIMON_GZIP to an explicit GNU gzip executable when it is not on PATH. | |
| EOF | |
| } | |
| fail() { | |
| echo "Error: $*" >&2 | |
| exit 1 | |
| } | |
| calculate_sha512() { | |
| local file=$1 | |
| if command -v sha512sum >/dev/null 2>&1; then | |
| sha512sum "${file}" | awk '{print $1}' | |
| elif command -v shasum >/dev/null 2>&1; then | |
| shasum -a 512 "${file}" | awk '{print $1}' | |
| else | |
| fail "sha512sum or shasum is required" | |
| fi | |
| } | |
| find_gnu_gzip() { | |
| local candidate | |
| local resolved | |
| local version_line | |
| local -a candidates | |
| if [[ -n "${PAIMON_GZIP:-}" ]]; then | |
| candidates=("${PAIMON_GZIP}") | |
| else | |
| candidates=(gzip ggzip) | |
| fi | |
| for candidate in "${candidates[@]}"; do | |
| if [[ -x "${candidate}" ]]; then | |
| resolved=${candidate} | |
| elif resolved=$(command -v "${candidate}" 2>/dev/null); then | |
| : | |
| else | |
| continue | |
| fi | |
| version_line=$("${resolved}" --version 2>/dev/null | sed -n '1p' || true) | |
| if [[ "${version_line}" =~ ^gzip[[:space:]][0-9] ]]; then | |
| printf '%s\n' "${resolved}" | |
| return 0 | |
| fi | |
| done | |
| fail "GNU gzip is required for reproducible source archives; on macOS run 'brew install gzip' and set PAIMON_GZIP to the Homebrew gzip executable" | |
| } | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --version) | |
| [[ $# -ge 2 ]] || fail "--version requires a value" | |
| RELEASE_VERSION=$2 | |
| shift 2 | |
| ;; | |
| --git-ref) | |
| [[ $# -ge 2 ]] || fail "--git-ref requires a value" | |
| GIT_REF=$2 | |
| shift 2 | |
| ;; | |
| --output-dir) | |
| [[ $# -ge 2 ]] || fail "--output-dir requires a value" | |
| OUTPUT_DIR=$2 | |
| shift 2 | |
| ;; | |
| --signing-key) | |
| [[ $# -ge 2 ]] || fail "--signing-key requires a value" | |
| SIGNING_KEY=$2 | |
| shift 2 | |
| ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| fail "unknown argument: $1" | |
| ;; | |
| esac | |
| done | |
| [[ -n "${RELEASE_VERSION}" ]] || fail "--version is required" | |
| [[ -n "${GIT_REF}" ]] || fail "--git-ref is required" | |
| [[ "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || | |
| fail "release version must use MAJOR.MINOR.PATCH format" | |
| git -C "${SOURCE_ROOT}" rev-parse --verify "${GIT_REF}^{commit}" >/dev/null 2>&1 || | |
| fail "Git ref does not resolve to a commit: ${GIT_REF}" | |
| for required_file in CMakeLists.txt LICENSE NOTICE; do | |
| git -C "${SOURCE_ROOT}" cat-file -e "${GIT_REF}:${required_file}" 2>/dev/null || | |
| fail "${required_file} is missing from ${GIT_REF}" | |
| done | |
| command -v python3 >/dev/null 2>&1 || fail "python3 is required" | |
| TEMP_DIR=$(mktemp -d) | |
| trap 'rm -rf "${TEMP_DIR}"' EXIT | |
| # Check the immutable ref with the same metadata rules as archive verification. | |
| mkdir -p "${TEMP_DIR}/metadata/docs/source/_static" | |
| for metadata_file in CMakeLists.txt docs/source/conf.py docs/source/_static/versions.json; do | |
| git -C "${SOURCE_ROOT}" show "${GIT_REF}:${metadata_file}" \ | |
| >"${TEMP_DIR}/metadata/${metadata_file}" | |
| done | |
| python3 "${SCRIPT_DIR}/bump_version.py" \ | |
| --root "${TEMP_DIR}/metadata" --check "${RELEASE_VERSION}" | |
| GZIP_BIN=$(find_gnu_gzip) | |
| ARTIFACT_NAME="apache-paimon-cpp-${RELEASE_VERSION}-src.tgz" | |
| ARCHIVE_ROOT="paimon-cpp-${RELEASE_VERSION}" | |
| mkdir -p "${OUTPUT_DIR}" | |
| OUTPUT_DIR=$(cd "${OUTPUT_DIR}" && pwd) | |
| for suffix in "" ".sha512" ".asc"; do | |
| [[ ! -e "${OUTPUT_DIR}/${ARTIFACT_NAME}${suffix}" ]] || | |
| fail "refusing to overwrite ${OUTPUT_DIR}/${ARTIFACT_NAME}${suffix}" | |
| done | |
| echo "Creating ${ARTIFACT_NAME} from ${GIT_REF}..." | |
| git -C "${SOURCE_ROOT}" -c tar.umask=0022 archive \ | |
| --format=tar \ | |
| --prefix="${ARCHIVE_ROOT}/" \ | |
| "${GIT_REF}" | | |
| ( | |
| unset GZIP | |
| "${GZIP_BIN}" --no-name --stdout -6 | |
| ) >"${TEMP_DIR}/${ARTIFACT_NAME}" | |
| SHA512=$(calculate_sha512 "${TEMP_DIR}/${ARTIFACT_NAME}") | |
| printf '%s %s\n' "${SHA512}" "${ARTIFACT_NAME}" \ | |
| >"${TEMP_DIR}/${ARTIFACT_NAME}.sha512" | |
| if [[ -n "${SIGNING_KEY}" ]]; then | |
| command -v gpg >/dev/null 2>&1 || fail "gpg is required to sign the artifact" | |
| echo "Signing ${ARTIFACT_NAME} with ${SIGNING_KEY}..." | |
| gpg --armor \ | |
| --local-user "${SIGNING_KEY}" \ | |
| --detach-sign \ | |
| --output "${TEMP_DIR}/${ARTIFACT_NAME}.asc" \ | |
| "${TEMP_DIR}/${ARTIFACT_NAME}" | |
| fi | |
| mv "${TEMP_DIR}/${ARTIFACT_NAME}" "${OUTPUT_DIR}/" | |
| mv "${TEMP_DIR}/${ARTIFACT_NAME}.sha512" "${OUTPUT_DIR}/" | |
| if [[ -n "${SIGNING_KEY}" ]]; then | |
| mv "${TEMP_DIR}/${ARTIFACT_NAME}.asc" "${OUTPUT_DIR}/" | |
| fi | |
| echo "Created release artifacts in ${OUTPUT_DIR}:" | |
| echo " ${ARTIFACT_NAME}" | |
| echo " ${ARTIFACT_NAME}.sha512" | |
| if [[ -n "${SIGNING_KEY}" ]]; then | |
| echo " ${ARTIFACT_NAME}.asc" | |
| else | |
| echo " Signature not created; pass --signing-key for an official release candidate." | |
| fi |