#!/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.
#
DRY_RUN=${DRY_RUN:-0}
GPG="gpg --pinentry-mode loopback --no-tty --batch"
YETUS_VERSION=${YETUS_VERSION:-0.11.1}
set -x
function error {
echo "$*"
exit 1
}
function read_config {
local PROMPT="$1"
local DEFAULT="$2"
local REPLY=
read -p "$PROMPT [$DEFAULT]: " REPLY
local RETVAL="${REPLY:-$DEFAULT}"
if [ -z "$RETVAL" ]; then
error "$PROMPT is must be provided."
fi
echo "$RETVAL"
}
function parse_version {
grep -e '.*' | \
head -n 2 | tail -n 1 | cut -d'>' -f2 | cut -d '<' -f1
}
function run_silent {
local BANNER="$1"
local LOG_FILE="$2"
shift 2
echo "========================"
echo "= $BANNER"
echo "Command: $*"
echo "Log file: $LOG_FILE"
"$@" 1>"$LOG_FILE" 2>&1
local EC=$?
if [ $EC != 0 ]; then
echo "Command FAILED. Check full logs for details."
tail "$LOG_FILE"
exit $EC
fi
}
function fcreate_secure {
local FPATH="$1"
rm -f "$FPATH"
touch "$FPATH"
chmod 600 "$FPATH"
}
function check_for_tag {
curl -s --head --fail "$ASF_GITHUB_REPO/releases/tag/$1" > /dev/null
}
# API compare version.
function get_api_diff_version {
local version=$1
local rev=$(echo "$version" | cut -d . -f 3)
local api_diff_tag
if [ $rev != 0 ]; then
local short_version=$(echo "$version" | cut -d . -f 1-2)
api_diff_tag="rel/${short_version}.$((rev - 1))"
else
local major=$(echo "$version" | cut -d . -f 1)
local minor=$(echo "$version" | cut -d . -f 2)
if [ $minor != 0 ]; then
api_diff_tag="rel/${major}.$((minor - 1)).0)"
else
api_diff_tag="rel/$((major - 1)).0.0)"
fi
fi
api_diff_tag=$(read_config "api_diff_tag", "$api_diff_tag")
echo $api_diff_tag
}
# Get all branches that begin with 'branch-', the hbase convention for
# release branches, sort them and then pop off the most recent.
function get_release_info {
export PROJECT=$(read_config "PROJECT" "$PROJECT")
if [[ -z "${ASF_REPO}" ]]; then
ASF_REPO="https://gitbox.apache.org/repos/asf/${PROJECT}.git"
fi
if [[ -z "${ASF_REPO_WEBUI}" ]]; then
ASF_REPO_WEBUI="https://gitbox.apache.org/repos/asf?p=${PROJECT}.git"
fi
if [[ -z "${ASF_GITHUB_REPO}" ]]; then
ASF_GITHUB_REPO="https://github.com/apache/${PROJECT}"
fi
if [ -z "$GIT_BRANCH" ]; then
# If no branch is specified, find out the latest branch from the repo.
GIT_BRANCH=$(git ls-remote --heads "$ASF_REPO" |
grep refs/heads/branch- |
awk '{print $2}' |
sort -r |
head -n 1 |
cut -d/ -f3)
fi
export GIT_BRANCH=$(read_config "GIT_BRANCH" "$GIT_BRANCH")
# Find the current version for the branch.
local VERSION=$(curl -s "$ASF_REPO_WEBUI;a=blob_plain;f=pom.xml;hb=refs/heads/$GIT_BRANCH" |
parse_version)
echo "Current branch VERSION is $VERSION."
NEXT_VERSION="$VERSION"
RELEASE_VERSION=""
SHORT_VERSION=$(echo "$VERSION" | cut -d . -f 1-2)
if [[ ! $VERSION =~ .*-SNAPSHOT ]]; then
RELEASE_VERSION="$VERSION"
else
RELEASE_VERSION="${VERSION/-SNAPSHOT/}"
fi
local REV=$(echo "$VERSION" | cut -d . -f 3)
# Find out what RC is being prepared.
# - If the current version is "x.y.0", then this is RC0 of the "x.y.0" release.
# - If not, need to check whether the previous version has been already released or not.
# - If it has, then we're building RC0 of the current version.
# - If it has not, we're building the next RC of the previous version.
local RC_COUNT
if [ $REV != 0 ]; then
local PREV_REL_REV=$((REV - 1))
PREV_REL_TAG="rel/${SHORT_VERSION}.${PREV_REL_REV}"
if check_for_tag "$PREV_REL_TAG"; then
RC_COUNT=0
REV=$((REV + 1))
NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT"
else
RELEASE_VERSION="${SHORT_VERSION}.${PREV_REL_REV}"
RC_COUNT=$(git ls-remote --tags "$ASF_REPO" "${RELEASE_VERSION}RC*" | wc -l)
# This makes a 'number' of it.
RC_COUNT=$((RC_COUNT))
fi
else
REV=$((REV + 1))
NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT"
RC_COUNT=0
fi
export RELEASE_VERSION=$(read_config "RELEASE_VERSION" "$RELEASE_VERSION")
export NEXT_VERSION=$(read_config "NEXT_VERSION" "$NEXT_VERSION")
RC_COUNT=$(read_config "RC_COUNT" "$RC_COUNT")
# Check if the RC already exists, and if re-creating the RC, skip tag creation.
RELEASE_TAG="${RELEASE_VERSION}RC${RC_COUNT}"
SKIP_TAG=0
if check_for_tag "$RELEASE_TAG"; then
read -p "$RELEASE_TAG already exists. Continue anyway [y/n]? " ANSWER
if [ "$ANSWER" != "y" ]; then
error "Exiting."
fi
SKIP_TAG=1
fi
export RELEASE_TAG
GIT_REF="$RELEASE_TAG"
if is_dry_run; then
echo "This is a dry run. Please confirm the ref that will be built for testing."
GIT_REF=$(read_config "GIT_REF" "$GIT_REF")
fi
export GIT_REF
export PACKAGE_VERSION="$RELEASE_TAG"
export API_DIFF_TAG=$(get_api_diff_version $RELEASE_VERSION)
# Gather some user information.
export ASF_USERNAME=$(read_config "ASF_USERNAME" "$LOGNAME")
GIT_NAME=$(git config user.name || echo "")
export GIT_NAME=$(read_config "GIT_NAME" "$GIT_NAME")
export GIT_EMAIL="$ASF_USERNAME@apache.org"
export GPG_KEY=$(read_config "GPG_KEY" "$GIT_EMAIL")
cat <&1 | cut -d " " -f 2)
echo "java version: $JAVA_VERSION"
export JAVA_VERSION
}
function init_python {
if ! [ -x "$(command -v python2)" ]; then
echo 'Error: python2 needed by yetus. Install or add link? E.g: sudo ln -sf /usr/bin/python2.7 /usr/local/bin/python2' >&2
exit 1
fi
echo "python version: `python2 --version`"
}
# Set MVN
function init_mvn {
if [ -n "$MAVEN_HOME" ]; then
MVN=${MAVEN_HOME}/bin/mvn
elif [ $(type -P mvn) ]; then
MVN=mvn
else
error "MAVEN_HOME is not set nor is mvn on the current path."
fi
echo "mvn version: `$MVN --version`"
# Add timestamped logging.
MVN="${MVN} -B"
export MVN
}
# Writes report into cwd!
function generate_api_report {
local project="$1"
local previous_tag="$2"
local release_tag="$3"
# Generate api report.
${project}/dev-support/checkcompatibility.py --annotation \
org.apache.yetus.audience.InterfaceAudience.Public \
$previous_tag $release_tag
local previous_version=$(echo ${previous_tag} | sed -e 's/rel\///')
cp ${project}/target/compat-check/report.html "./api_compare_${previous_version}_to_${release_tag}.html"
}
# Update the CHANGES.md
# DOES NOT DO COMMITS! Caller should do that.
# yetus requires python2 to be on the path.
function update_releasenotes {
local project="$1"
local release_version="$2"
local yetus="apache-yetus-${YETUS_VERSION}"
wget -qO- "https://www.apache.org/dyn/mirrors/mirrors.cgi?action=download&filename=/yetus/${YETUS_VERSION}/${yetus}-bin.tar.gz" | \
tar xvz -C . || exit
cd ./${yetus} || exit
./bin/releasedocmaker -p HBASE --fileversions -v ${release_version} -l --sortorder=newer --skip-credits
# First clear out the changes written by previous RCs.
pwd
sed -i -e "/^## Release ${release_version}/,/^## Release/ {//!d; /^## Release ${release_version}/d;}" \
${project}/CHANGES.md || true
sed -i -e "/^# HBASE ${release_version} Release Notes/,/^# HBASE/{//!d; /^# HBASE ${release_version} Release Notes/d;}" \
${project}/RELEASENOTES.md || true
# The above generates RELEASENOTES.X.X.X.md and CHANGELOG.X.X.X.md.
# To insert into project CHANGES.me...need to cut the top off the
# CHANGELOG.X.X.X.md file removing license and first line and then
# insert it after the license comment closing where we have a
# DO NOT REMOVE marker text!
sed -i -e '/## Release/,$!d' CHANGELOG.${release_version}.md
sed -i -e "/DO NOT REMOVE/r CHANGELOG.${release_version}.md" ${project}/CHANGES.md
# Similar for RELEASENOTES but slightly different.
sed -i -e '/Release Notes/,$!d' RELEASENOTES.${release_version}.md
sed -i -e "/DO NOT REMOVE/r RELEASENOTES.${release_version}.md" ${project}/RELEASENOTES.md
cd .. || exit
}
# Make src release.
# Takes as arguments first the project name -- e.g. hbase or hbase-operator-tools
# -- and then the version string. Expects to find checkout adjacent to this script
# named for 'project', the first arg passed.
# Expects the following three defines in the environment:
# - GPG needs to be defined, with the path to GPG: defaults 'gpg'.
# - The passphrase in the GPG_PASSPHRASE variable: no default (we don't make .asc file).
# - GIT_REF which is the tag to create the tgz from: defaults to 'master'.
# For example:
# $ GPG_PASSPHRASE="XYZ" GIT_REF="master" make_src_release hbase-operator-tools 1.0.0
make_src_release() {
# Tar up the src and sign and hash it.
project="${1}"
version="${2}"
basename="${project}-${version}"
rm -rf "${basename}-src*"
tgz="${basename}-src.tar.gz"
cd "${project}" || exit
git clean -d -f -x
git archive --format=tar.gz --output="../${tgz}" --prefix="${basename}/" "${GIT_REF:-master}"
cd .. || exit
echo "$GPG_PASSPHRASE" | $GPG --passphrase-fd 0 --armour --output "${tgz}.asc" \
--detach-sig "${tgz}"
echo "$GPG_PASSPHRASE" | $GPG --passphrase-fd 0 --print-md SHA512 "${tgz}" > "${tgz}.sha512"
}
# Make binary release.
# Takes as arguments first the project name -- e.g. hbase or hbase-operator-tools
# -- and then the version string. Expects to find checkout adjacent to this script
# named for 'project', the first arg passed.
# Expects the following three defines in the environment:
# - GPG needs to be defined, with the path to GPG: defaults 'gpg'.
# - The passphrase in the GPG_PASSPHRASE variable: no default (we don't make .asc file).
# - GIT_REF which is the tag to create the tgz from: defaults to 'master'.
# - MVN Default is 'mvn'.
# For example:
# $ GPG_PASSPHRASE="XYZ" GIT_REF="master" make_src_release hbase-operator-tools 1.0.0
make_binary_release() {
project="${1}"
version="${2}"
basename="${project}-${version}"
rm -rf "${basename}-bin*"
cd $project || exit
git clean -d -f -x
# Three invocations of maven. This seems to work. One to
# populate the repo, another to build the site, and then
# a third to assemble the binary artifact. Trying to do
# all in the one invocation fails; a problem in our
# assembly spec to in maven. TODO. Meantime, three invocations.
MAVEN_OPTS="${MAVEN_OPTS}" ${MVN} --settings $tmp_settings clean install -DskipTests \
-Dmaven.repo.local="${tmp_repo}"
MAVEN_OPTS="${MAVEN_OPTS}" ${MVN} --settings $tmp_settings site -DskipTests \
-Dmaven.repo.local="${tmp_repo}"
MAVEN_OPTS="${MAVEN_OPTS}" ${MVN} --settings $tmp_settings install assembly:single -DskipTests \
-Dcheckstyle.skip=true "${PUBLISH_PROFILES}" -Dmaven.repo.local="${tmp_repo}"
# Check there is a bin gz output. The build may not produce one: e.g. hbase-thirdparty.
f_bin_tgz="./${PROJECT}-assembly/target/${basename}*-bin.tar.gz"
if ls ${f_bin_tgz} &>/dev/null; then
cp ${f_bin_tgz} ..
cd .. || exit
for i in "${basename}"*-bin.tar.gz; do
echo "$GPG_PASSPHRASE" | $GPG --passphrase-fd 0 --armour --output "$i.asc" --detach-sig "$i"
echo "$GPG_PASSPHRASE" | $GPG --passphrase-fd 0 --print-md SHA512 "${i}" > "$i.sha512"
done
else
cd .. || exit
echo "No ${f_bin_tgz} product; expected?"
fi
}