#!/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 }