HBASE-24572 release scripts should try to use a keyid when refering to GPG keys....
[hbase.git] / dev-support / create-release / release-util.sh
blob1f618f20051faf8907ea5762e42c0aa9b8577249
1 #!/usr/bin/env bash
4 # Licensed to the Apache Software Foundation (ASF) under one or more
5 # contributor license agreements. See the NOTICE file distributed with
6 # this work for additional information regarding copyright ownership.
7 # The ASF licenses this file to You under the Apache License, Version 2.0
8 # (the "License"); you may not use this file except in compliance with
9 # the License. You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 DRY_RUN=${DRY_RUN:-1} #default to dry run
20 DEBUG=${DEBUG:-0}
21 GPG=${GPG:-gpg}
22 GPG_ARGS=(--no-autostart --batch)
23 if [ -n "${GPG_KEY}" ]; then
24 GPG_ARGS=("${GPG_ARGS[@]}" --local-user "${GPG_KEY}")
26 # Maven Profiles for publishing snapshots and release to Maven Central and Dist
27 PUBLISH_PROFILES=("-P" "apache-release,release")
29 set -e
31 function error {
32 echo "Error: $*" >&2
33 exit 1
36 function read_config {
37 local PROMPT="$1"
38 local DEFAULT="$2"
39 local REPLY=
41 read -r -p "$PROMPT [$DEFAULT]: " REPLY
42 local RETVAL="${REPLY:-$DEFAULT}"
43 if [ -z "$RETVAL" ]; then
44 error "$PROMPT must be provided."
46 echo "$RETVAL"
49 function parse_version {
50 grep -e '<version>.*</version>' | \
51 head -n 2 | tail -n 1 | cut -d'>' -f2 | cut -d '<' -f1
54 function banner {
55 local msg="$1"
56 echo "========================"
57 echo "=== ${msg}"
58 echo
61 function run_silent {
62 local BANNER="$1"
63 local LOG_FILE="$2"
64 shift 2
66 banner "${BANNER}"
67 echo "Command: $*"
68 echo "Log file: $LOG_FILE"
70 if ! "$@" 1>"$LOG_FILE" 2>&1; then
71 echo "Command FAILED. Check full logs for details."
72 tail "$LOG_FILE"
73 exit 1
75 echo "=== SUCCESS"
78 function fcreate_secure {
79 local FPATH="$1"
80 rm -f "$FPATH"
81 touch "$FPATH"
82 chmod 600 "$FPATH"
85 function check_for_tag {
86 curl -s --head --fail "$ASF_GITHUB_REPO/releases/tag/$1" > /dev/null
89 function wait_for_tag {
90 # Confirm the tag synchronizes to github. This can take a couple minutes,
91 # but usually it just takes a few seconds.
92 local max_propagation_time=300
93 local prop_delay=30
94 while ! check_for_tag "$1"; do
95 if (( max_propagation_time <= 0 )); then
96 echo "ERROR: Taking more than 5 minutes to propagate Release Tag $1 to github mirror." >&2
97 echo "Please wait and resume other create-release steps when $1 is available in github." >&2
98 exit 1
100 echo "Waiting up to $max_propagation_time seconds for tag to propagate to github mirror..."
101 sleep $prop_delay
102 max_propagation_time=$((max_propagation_time - prop_delay))
103 done
106 # API compare version.
107 function get_api_diff_version {
108 local version="$1"
109 local rev
110 local api_diff_tag
111 rev=$(echo "$version" | cut -d . -f 3)
112 if [ "$rev" != 0 ]; then
113 local short_version
114 short_version="$(echo "$version" | cut -d . -f 1-2)"
115 api_diff_tag="rel/${short_version}.$((rev - 1))"
116 else
117 local major minor
118 major="$(echo "$version" | cut -d . -f 1)"
119 minor="$(echo "$version" | cut -d . -f 2)"
120 if [ "$minor" != 0 ]; then
121 api_diff_tag="rel/${major}.$((minor - 1)).0"
122 else
123 api_diff_tag="rel/$((major - 1)).0.0"
126 api_diff_tag="$(read_config "api_diff_tag" "$api_diff_tag")"
127 echo "$api_diff_tag"
130 # Get all branches that begin with 'branch-', the hbase convention for
131 # release branches, sort them and then pop off the most recent.
132 function get_release_info {
133 PROJECT="$(read_config "PROJECT" "$PROJECT")"
134 export PROJECT
136 if [[ -z "${ASF_REPO}" ]]; then
137 ASF_REPO="https://gitbox.apache.org/repos/asf/${PROJECT}.git"
139 if [[ -z "${ASF_REPO_WEBUI}" ]]; then
140 ASF_REPO_WEBUI="https://gitbox.apache.org/repos/asf?p=${PROJECT}.git"
142 if [[ -z "${ASF_GITHUB_REPO}" ]]; then
143 ASF_GITHUB_REPO="https://github.com/apache/${PROJECT}"
145 if [ -z "$GIT_BRANCH" ]; then
146 # If no branch is specified, find out the latest branch from the repo.
147 GIT_BRANCH="$(git ls-remote --heads "$ASF_REPO" |
148 grep refs/heads/branch- |
149 awk '{print $2}' |
150 sort -r |
151 head -n 1 |
152 cut -d/ -f3)"
155 GIT_BRANCH="$(read_config "GIT_BRANCH" "$GIT_BRANCH")"
156 export GIT_BRANCH
158 # Find the current version for the branch.
159 local version
160 version="$(curl -s "$ASF_REPO_WEBUI;a=blob_plain;f=pom.xml;hb=refs/heads/$GIT_BRANCH" |
161 parse_version)"
162 echo "Current branch VERSION is $version."
164 NEXT_VERSION="$version"
165 RELEASE_VERSION=""
166 SHORT_VERSION="$(echo "$version" | cut -d . -f 1-2)"
167 if [[ ! "$version" =~ .*-SNAPSHOT ]]; then
168 RELEASE_VERSION="$version"
169 else
170 RELEASE_VERSION="${version/-SNAPSHOT/}"
173 local REV
174 REV="$(echo "${RELEASE_VERSION}" | cut -d . -f 3)"
176 # Find out what RC is being prepared.
177 # - If the current version is "x.y.0", then this is RC0 of the "x.y.0" release.
178 # - If not, need to check whether the previous version has been already released or not.
179 # - If it has, then we're building RC0 of the current version.
180 # - If it has not, we're building the next RC of the previous version.
181 local RC_COUNT
182 if [ "$REV" != 0 ]; then
183 local PREV_REL_REV=$((REV - 1))
184 PREV_REL_TAG="rel/${SHORT_VERSION}.${PREV_REL_REV}"
185 if check_for_tag "$PREV_REL_TAG"; then
186 RC_COUNT=0
187 REV=$((REV + 1))
188 NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT"
189 else
190 RELEASE_VERSION="${SHORT_VERSION}.${PREV_REL_REV}"
191 RC_COUNT="$(git ls-remote --tags "$ASF_REPO" "${RELEASE_VERSION}RC*" | wc -l)"
192 # This makes a 'number' of it.
193 RC_COUNT=$((RC_COUNT))
195 else
196 REV=$((REV + 1))
197 NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT"
198 RC_COUNT=0
201 RELEASE_VERSION="$(read_config "RELEASE_VERSION" "$RELEASE_VERSION")"
202 NEXT_VERSION="$(read_config "NEXT_VERSION" "$NEXT_VERSION")"
203 export RELEASE_VERSION NEXT_VERSION
205 RC_COUNT="$(read_config "RC_COUNT" "$RC_COUNT")"
206 RELEASE_TAG="${RELEASE_VERSION}RC${RC_COUNT}"
207 RELEASE_TAG="$(read_config "RELEASE_TAG" "$RELEASE_TAG")"
209 # Check if the RC already exists, and if re-creating the RC, skip tag creation.
210 SKIP_TAG=0
211 if check_for_tag "$RELEASE_TAG"; then
212 read -r -p "$RELEASE_TAG already exists. Continue anyway [y/n]? " ANSWER
213 if [ "$ANSWER" != "y" ]; then
214 echo "Exiting."
215 exit 1
217 SKIP_TAG=1
220 export RELEASE_TAG SKIP_TAG
222 GIT_REF="$RELEASE_TAG"
223 if is_dry_run; then
224 echo "This is a dry run. If tag does not actually exist, please confirm the ref that will be built for testing."
225 GIT_REF="$(read_config "GIT_REF" "$GIT_REF")"
227 export GIT_REF
229 API_DIFF_TAG="$(get_api_diff_version "$RELEASE_VERSION")"
231 # Gather some user information.
232 ASF_USERNAME="$(read_config "ASF_USERNAME" "$LOGNAME")"
234 GIT_NAME="$(git config user.name || echo "")"
235 GIT_NAME="$(read_config "GIT_NAME" "$GIT_NAME")"
237 GIT_EMAIL="$ASF_USERNAME@apache.org"
238 GPG_KEY="$(read_config "GPG_KEY" "$GIT_EMAIL")"
239 if ! GPG_KEY_ID=$("${GPG}" "${GPG_ARGS[@]}" --keyid-format 0xshort --list-public-key "${GPG_KEY}" | grep "\[S\]" | grep -o "0x[0-9A-F]*") ||
240 [ -z "${GPG_KEY_ID}" ] ; then
241 GPG_KEY_ID=$("${GPG}" "${GPG_ARGS[@]}" --keyid-format 0xshort --list-public-key "${GPG_KEY}" | head -n 1 | grep -o "0x[0-9A-F]*" || true)
243 read -r -p "We think the key '${GPG_KEY}' corresponds to the key id '${GPG_KEY_ID}'. Is this correct [y/n]? " ANSWER
244 if [ "$ANSWER" = "y" ]; then
245 GPG_KEY="${GPG_KEY_ID}"
247 export API_DIFF_TAG ASF_USERNAME GIT_NAME GIT_EMAIL GPG_KEY
249 cat <<EOF
250 ================
251 Release details:
252 GIT_BRANCH: $GIT_BRANCH
253 RELEASE_VERSION: $RELEASE_VERSION
254 NEXT_VERSION: $NEXT_VERSION
255 RELEASE_TAG: $RELEASE_TAG $([[ "$GIT_REF" != "$RELEASE_TAG" ]] && printf "\n%s\n" "GIT_REF: $GIT_REF")
256 API_DIFF_TAG: $API_DIFF_TAG
257 ASF_USERNAME: $ASF_USERNAME
258 GPG_KEY: $GPG_KEY
259 GIT_NAME: $GIT_NAME
260 GIT_EMAIL: $GIT_EMAIL
261 DRY_RUN: $(is_dry_run && echo "yes" || echo "NO, THIS BUILD WILL BE PUBLISHED!")
262 ================
265 read -r -p "Is this info correct [y/n]? " ANSWER
266 if [ "$ANSWER" != "y" ]; then
267 echo "Exiting."
268 exit 1
270 GPG_ARGS=("${GPG_ARGS[@]}" --local-user "${GPG_KEY}")
272 if ! is_dry_run; then
273 if [ -z "$ASF_PASSWORD" ]; then
274 stty -echo && printf "ASF_PASSWORD: " && read -r ASF_PASSWORD && printf '\n' && stty echo
276 else
277 ASF_PASSWORD="***INVALID***"
280 export ASF_PASSWORD
283 function is_dry_run {
284 [[ "$DRY_RUN" = 1 ]]
287 function is_debug {
288 [[ "${DEBUG}" = 1 ]]
291 function check_get_passwords {
292 for env in "$@"; do
293 if [ -z "${!env}" ]; then
294 echo "The environment variable $env is not set. Please enter the password or passphrase."
295 echo
296 # shellcheck disable=SC2229
297 stty -echo && printf "%s : " "$env" && read -r "$env" && printf '\n' && stty echo
299 # shellcheck disable=SC2163
300 export "$env"
301 done
304 function check_needed_vars {
305 local missing=0
306 for env in "$@"; do
307 if [ -z "${!env}" ]; then
308 echo "$env must be set to run this script"
309 (( missing++ ))
310 else
311 # shellcheck disable=SC2163
312 export "$env"
314 done
315 (( missing > 0 )) && exit_with_usage
316 return 0
319 function init_locale {
320 local locale_value
321 OS="$(uname -s)"
322 case "${OS}" in
323 Darwin*) locale_value="en_US.UTF-8";;
324 Linux*) locale_value="C.UTF-8";;
325 *) error "unknown OS";;
326 esac
327 export LC_ALL="$locale_value"
328 export LANG="$locale_value"
331 # Initializes JAVA_VERSION to the version of the JVM in use.
332 function init_java {
333 if [ -z "$JAVA_HOME" ]; then
334 error "JAVA_HOME is not set."
336 JAVA_VERSION=$("${JAVA_HOME}"/bin/javac -version 2>&1 | cut -d " " -f 2)
337 echo "java version: $JAVA_VERSION"
338 export JAVA_VERSION
341 function init_python {
342 if ! [ -x "$(command -v python2)" ]; then
343 error 'python2 needed by yetus. Install or add link? E.g: sudo ln -sf /usr/bin/python2.7 /usr/local/bin/python2'
345 echo "python version: $(python2 --version)"
348 # Set MVN
349 function init_mvn {
350 if [ -n "$MAVEN_HOME" ]; then
351 MVN=("${MAVEN_HOME}/bin/mvn")
352 elif [ "$(type -P mvn)" ]; then
353 MVN=(mvn)
354 else
355 error "MAVEN_HOME is not set nor is mvn on the current path."
357 # Add batch mode.
358 MVN=("${MVN[@]}" -B)
359 export MVN
360 echo -n "mvn version: "
361 "${MVN[@]}" --version
362 configure_maven
365 function init_yetus {
366 declare YETUS_VERSION
367 if [ -z "${YETUS_HOME}" ]; then
368 error "Missing Apache Yetus."
370 # Work around yetus bug by asking test-patch for the version instead of rdm.
371 YETUS_VERSION=$("${YETUS_HOME}/bin/test-patch" --version)
372 echo "Apache Yetus version ${YETUS_VERSION}"
375 function configure_maven {
376 # Add timestamps to mvn logs.
377 MAVEN_OPTS="-Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss ${MAVEN_OPTS}"
378 # Suppress gobs of "Download from central:" messages
379 MAVEN_OPTS="-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn ${MAVEN_OPTS}"
380 MAVEN_LOCAL_REPO="${REPO:-$(pwd)/$(mktemp -d hbase-repo-XXXXX)}"
381 [[ -d "$MAVEN_LOCAL_REPO" ]] || mkdir -p "$MAVEN_LOCAL_REPO"
382 MAVEN_SETTINGS_FILE="${MAVEN_LOCAL_REPO}/tmp-settings.xml"
383 MVN=("${MVN[@]}" --settings "${MAVEN_SETTINGS_FILE}")
384 export MVN MAVEN_OPTS MAVEN_SETTINGS_FILE MAVEN_LOCAL_REPO
385 export ASF_USERNAME ASF_PASSWORD
386 # reference passwords from env rather than storing in the settings.xml file.
387 cat <<'EOF' > "$MAVEN_SETTINGS_FILE"
388 <?xml version="1.0" encoding="UTF-8"?>
389 <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
390 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
391 xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
392 <localRepository>/${env.MAVEN_LOCAL_REPO}</localRepository>
393 <servers>
394 <server><id>apache.snapshots.https</id><username>${env.ASF_USERNAME}</username>
395 <password>${env.ASF_PASSWORD}</password></server>
396 <server><id>apache.releases.https</id><username>${env.ASF_USERNAME}</username>
397 <password>${env.ASF_PASSWORD}</password></server>
398 </servers>
399 <profiles>
400 <profile>
401 <activation>
402 <activeByDefault>true</activeByDefault>
403 </activation>
404 <properties>
405 <gpg.keyname>${env.GPG_KEY}</gpg.keyname>
406 </properties>
407 </profile>
408 </profiles>
409 </settings>
413 # clone of the repo, deleting anything that exists in the working directory named after the project.
414 # optionally with auth details for pushing.
415 function git_clone_overwrite {
416 local asf_repo
417 if [ -z "${PROJECT}" ] || [ "${PROJECT}" != "${PROJECT#/}" ]; then
418 error "Project name must be defined and not start with a '/'. PROJECT='${PROJECT}'"
420 rm -rf "${PROJECT}"
422 if [[ -z "${GIT_REPO}" ]]; then
423 asf_repo="gitbox.apache.org/repos/asf/${PROJECT}.git"
424 echo "[INFO] clone will be of the gitbox repo for ${PROJECT}."
425 if [ -n "${ASF_USERNAME}" ] && [ -n "${ASF_PASSWORD}" ]; then
426 # Ugly!
427 encoded_username=$(python -c "import urllib; print urllib.quote('''$ASF_USERNAME''', '')")
428 encoded_password=$(python -c "import urllib; print urllib.quote('''$ASF_PASSWORD''', '')")
429 GIT_REPO="https://$encoded_username:$encoded_password@${asf_repo}"
430 else
431 GIT_REPO="https://${asf_repo}"
433 else
434 echo "[INFO] clone will be of provided git repo."
436 # N.B. we use the shared flag because the clone is short lived and if a local repo repo was
437 # given this will let us refer to objects there directly instead of hardlinks or copying.
438 # The option is silently ignored for non-local repositories. see the note on git help clone
439 # for the --shared option for details.
440 git clone --shared -b "${GIT_BRANCH}" -- "${GIT_REPO}" "${PROJECT}"
441 # If this was a host local git repo then add in an alternates and remote that will
442 # work back on the host if the RM needs to do any post-processing steps, i.e. pushing the git tag
443 # for more info see 'git help remote' and 'git help repository-layout'.
444 if [ -n "$HOST_GIT_REPO" ]; then
445 echo "${HOST_GIT_REPO}/objects" >> "${PROJECT}/.git/objects/info/alternates"
446 (cd "${PROJECT}"; git remote add host "${HOST_GIT_REPO}")
450 # Writes report into cwd!
451 # TODO should have option for maintenance release that include LimitedPrivate in report
452 function generate_api_report {
453 local project="$1"
454 local previous_tag="$2"
455 local release_tag="$3"
456 local previous_version
457 # Generate api report.
458 "${project}"/dev-support/checkcompatibility.py --annotation \
459 org.apache.yetus.audience.InterfaceAudience.Public \
460 "$previous_tag" "$release_tag"
461 previous_version="$(echo "${previous_tag}" | sed -e 's/rel\///')"
462 cp "${project}/target/compat-check/report.html" "./api_compare_${previous_version}_to_${release_tag}.html"
465 # Look up the Jira name associated with project.
466 # Currently all the 'hbase-*' projects share the same HBASE jira name. This works because,
467 # by convention, the HBASE jira "Fix Version" field values have the sub-project name pre-pended,
468 # as in "hbase-operator-tools-1.0.0".
469 # TODO: For non-hbase-related projects, enhance this to use Jira API query instead of text lookup.
470 function get_jira_name {
471 local project="$1"
472 local jira_name
473 case "${project}" in
474 hbase*) jira_name="HBASE";;
475 *) jira_name="";;
476 esac
477 if [[ -z "$jira_name" ]]; then
478 error "Sorry, can't determine the Jira name for project $project"
480 echo "$jira_name"
483 # Update the CHANGES.md
484 # DOES NOT DO COMMITS! Caller should do that.
485 # requires yetus to have a defined home already.
486 # yetus requires python2 to be on the path.
487 function update_releasenotes {
488 local project_dir="$1"
489 local jira_fix_version="$2"
490 local jira_project
491 jira_project="$(get_jira_name "$(basename "$project_dir")")"
492 "${YETUS_HOME}/bin/releasedocmaker" -p "${jira_project}" --fileversions -v "${jira_fix_version}" \
493 -l --sortorder=newer --skip-credits
495 # First clear out the changes written by previous RCs.
496 if [ -f "${project_dir}/CHANGES.md" ]; then
497 sed -i -e \
498 "/^## Release ${jira_fix_version}/,/^## Release/ {//!d; /^## Release ${jira_fix_version}/d;}" \
499 "${project_dir}/CHANGES.md" || true
501 if [ -f "${project_dir}/RELEASENOTES.md" ]; then
502 sed -i -e \
503 "/^# ${jira_project} ${jira_fix_version} Release Notes/,/^# ${jira_project}/{//!d; /^# ${jira_project} ${jira_fix_version} Release Notes/d;}" \
504 "${project_dir}/RELEASENOTES.md" || true
507 # The releasedocmaker call above generates RELEASENOTES.X.X.X.md and CHANGELOG.X.X.X.md.
508 if [ -f "${project_dir}/CHANGES.md" ]; then
509 # To insert into project's CHANGES.md...need to cut the top off the
510 # CHANGELOG.X.X.X.md file removing license and first line and then
511 # insert it after the license comment closing where we have a
512 # DO NOT REMOVE marker text!
513 sed -i -e '/## Release/,$!d' "CHANGELOG.${jira_fix_version}.md"
514 sed -i -e "/DO NOT REMOVE/r CHANGELOG.${jira_fix_version}.md" "${project_dir}/CHANGES.md"
515 else
516 mv "CHANGELOG.${jira_fix_version}.md" "${project_dir}/CHANGES.md"
518 if [ -f "${project_dir}/RELEASENOTES.md" ]; then
519 # Similar for RELEASENOTES but slightly different.
520 sed -i -e '/Release Notes/,$!d' "RELEASENOTES.${jira_fix_version}.md"
521 sed -i -e "/DO NOT REMOVE/r RELEASENOTES.${jira_fix_version}.md" \
522 "${project_dir}/RELEASENOTES.md"
523 else
524 mv "RELEASENOTES.${jira_fix_version}.md" "${project_dir}/RELEASENOTES.md"
528 # Make src release.
529 # Takes as arguments first the project name -- e.g. hbase or hbase-operator-tools
530 # -- and then the version string. Expects to find checkout adjacent to this script
531 # named for 'project', the first arg passed.
532 # Expects the following three defines in the environment:
533 # - GPG needs to be defined, with the path to GPG: defaults 'gpg'.
534 # - GIT_REF which is the tag to create the tgz from: defaults to 'master'.
535 # For example:
536 # $ GIT_REF="master" make_src_release hbase-operator-tools 1.0.0
537 make_src_release() {
538 # Tar up the src and sign and hash it.
539 local project="${1}"
540 local version="${2}"
541 local base_name="${project}-${version}"
542 rm -rf "${base_name}"-src*
543 tgz="${base_name}-src.tar.gz"
544 cd "${project}" || exit
545 git clean -d -f -x
546 git archive --format=tar.gz --output="../${tgz}" --prefix="${base_name}/" "${GIT_REF:-master}"
547 cd .. || exit
548 $GPG "${GPG_ARGS[@]}" --armor --output "${tgz}.asc" --detach-sig "${tgz}"
549 $GPG "${GPG_ARGS[@]}" --print-md SHA512 "${tgz}" > "${tgz}.sha512"
552 # Make binary release.
553 # Takes as arguments first the project name -- e.g. hbase or hbase-operator-tools
554 # -- and then the version string. Expects to find checkout adjacent to this script
555 # named for 'project', the first arg passed.
556 # Expects the following three defines in the environment:
557 # - GPG needs to be defined, with the path to GPG: defaults 'gpg'.
558 # - GIT_REF which is the tag to create the tgz from: defaults to 'master'.
559 # - MVN Default is "mvn -B --settings $MAVEN_SETTINGS_FILE".
560 # For example:
561 # $ GIT_REF="master" make_src_release hbase-operator-tools 1.0.0
562 make_binary_release() {
563 local project="${1}"
564 local version="${2}"
565 local base_name="${project}-${version}"
566 rm -rf "${base_name}"-bin*
567 cd "$project" || exit
569 git clean -d -f -x
570 # Three invocations of maven. This seems to work. One to
571 # populate the repo, another to build the site, and then
572 # a third to assemble the binary artifact. Trying to do
573 # all in the one invocation fails; a problem in our
574 # assembly spec to in maven. TODO. Meantime, three invocations.
575 "${MVN[@]}" clean install -DskipTests
576 "${MVN[@]}" site -DskipTests
577 kick_gpg_agent
578 "${MVN[@]}" install assembly:single -DskipTests -Dcheckstyle.skip=true "${PUBLISH_PROFILES[@]}"
580 # Check there is a bin gz output. The build may not produce one: e.g. hbase-thirdparty.
581 local f_bin_prefix="./${PROJECT}-assembly/target/${base_name}"
582 if ls "${f_bin_prefix}"*-bin.tar.gz &>/dev/null; then
583 cp "${f_bin_prefix}"*-bin.tar.gz ..
584 cd .. || exit
585 for i in "${base_name}"*-bin.tar.gz; do
586 "${GPG}" "${GPG_ARGS[@]}" --armour --output "${i}.asc" --detach-sig "${i}"
587 "${GPG}" "${GPG_ARGS[@]}" --print-md SHA512 "${i}" > "${i}.sha512"
588 done
589 else
590 cd .. || exit
591 echo "No ${f_bin_prefix}*-bin.tar.gz product; expected?"
595 # "Wake up" the gpg agent so it responds properly to maven-gpg-plugin, and doesn't cause timeout.
596 # Specifically this is done between invocation of 'mvn site' and 'mvn assembly:single', because
597 # the 'site' build takes long enough that the gpg-agent does become unresponsive and the following
598 # 'assembly' build (where gpg signing occurs) experiences timeout, without this "kick".
599 function kick_gpg_agent {
600 # All that's needed is to run gpg on a random file
601 # TODO could we just call gpg-connect-agent /bye
602 local i
603 i="$(mktemp)"
604 echo "This is a test file" > "$i"
605 "${GPG}" "${GPG_ARGS[@]}" --armour --output "${i}.asc" --detach-sig "${i}"
606 rm "$i" "$i.asc"
609 # Do maven command to set version into local pom
610 function maven_set_version { #input: <version_to_set>
611 local this_version="$1"
612 echo "${MVN[@]}" versions:set -DnewVersion="$this_version"
613 "${MVN[@]}" versions:set -DnewVersion="$this_version" | grep -v "no value" # silence logs
616 # Do maven command to read version from local pom
617 function maven_get_version {
618 # shellcheck disable=SC2016
619 "${MVN[@]}" -q -N -Dexec.executable="echo" -Dexec.args='${project.version}' exec:exec
622 # Do maven deploy to snapshot or release artifact repository, with checks.
623 function maven_deploy { #inputs: <snapshot|release> <log_file_path>
624 # Invoke with cwd=$PROJECT
625 local deploy_type="$1"
626 local mvn_log_file="$2" #secondary log file used later to extract staged_repo_id
627 if [[ "$deploy_type" != "snapshot" && "$deploy_type" != "release" ]]; then
628 error "unrecognized deploy type, must be 'snapshot'|'release'"
630 if [[ -z "$mvn_log_file" ]] || ! touch "$mvn_log_file"; then
631 error "must provide writable maven log output filepath"
633 # shellcheck disable=SC2153
634 if [[ "$deploy_type" == "snapshot" ]] && ! [[ "$RELEASE_VERSION" =~ -SNAPSHOT$ ]]; then
635 error "Snapshots must have a version with suffix '-SNAPSHOT'; you gave version '$RELEASE_VERSION'"
636 elif [[ "$deploy_type" == "release" ]] && [[ "$RELEASE_VERSION" =~ SNAPSHOT ]]; then
637 error "Non-snapshot release version must not include the word 'SNAPSHOT'; you gave version '$RELEASE_VERSION'"
639 # Publish ${PROJECT} to Maven repo
640 # shellcheck disable=SC2154
641 echo "Publishing ${PROJECT} checkout at '$GIT_REF' ($git_hash)"
642 echo "Publish version is $RELEASE_VERSION"
643 # Coerce the requested version
644 maven_set_version "$RELEASE_VERSION"
645 # Prepare for signing
646 kick_gpg_agent
647 declare -a mvn_goals=(clean install)
648 if ! is_dry_run; then
649 mvn_goals=("${mvn_goals[@]}" deploy)
651 echo "${MVN[@]}" -DskipTests -Dcheckstyle.skip=true "${PUBLISH_PROFILES[@]}" \
652 "${mvn_goals[@]}"
653 echo "Logging to ${mvn_log_file}. This will take a while..."
654 rm -f "$mvn_log_file"
655 # The tortuous redirect in the next command allows mvn's stdout and stderr to go to mvn_log_file,
656 # while also sending stderr back to the caller.
657 # shellcheck disable=SC2094
658 if ! "${MVN[@]}" -DskipTests -Dcheckstyle.skip=true "${PUBLISH_PROFILES[@]}" \
659 "${mvn_goals[@]}" 1>> "$mvn_log_file" 2> >( tee -a "$mvn_log_file" >&2 ); then
660 error "Deploy build failed, for details see log at '$mvn_log_file'."
662 echo "BUILD SUCCESS."
663 return 0
666 # guess the host os
667 # * DARWIN
668 # * LINUX
669 function get_host_os() {
670 uname -s | tr '[:lower:]' '[:upper:]'