release.sh: Add --quiet and --porcelain
[tools.git] / release-tools / release.sh
index a239e94b3375cac5f9e686e4b2848c193afce010..cf4989047e7b7f70fee27db671cf93b5c3b36703 100755 (executable)
@@ -1,5 +1,5 @@
 #! /bin/bash -e
-# Copyright 2020-2022 The OpenSSL Project Authors. All Rights Reserved.
+# Copyright 2020-2023 The OpenSSL Project Authors. All Rights Reserved.
 #
 # Licensed under the Apache License 2.0 (the "License").  You may not use
 # this file except in compliance with the License.  You can obtain a copy
@@ -33,10 +33,15 @@ Usage: release.sh [ options ... ]
                 For the purpose of signing tags and tar files, use this
                 key (default: use the default e-mail address’ key).
 
---no-upload     Don't upload to upload@dev.openssl.org.
+--upload-address=<address>
+                The location to upload release files to (default:
+                upload@dev.openssl.org)
+--no-upload     Don't upload the release files.
 --no-update     Don't perform 'make update' and 'make update-fips-checksums'.
+--quiet         Really quiet, only the final output will still be output.
 --verbose       Verbose output.
 --debug         Include debug output.  Implies --no-upload.
+--porcelain     Give the output in an easy-to-parse format for scripts.
 
 --force         Force execution
 
@@ -59,9 +64,11 @@ warn_branch=false
 do_clean=true
 do_upload=true
 do_update=true
+ECHO=echo
 DEBUG=:
 VERBOSE=:
 git_quiet=-q
+do_porcelain=false
 
 force=false
 
@@ -76,8 +83,10 @@ upload_address=upload@dev.openssl.org
 
 TEMP=$(getopt -l 'alpha,next-beta,beta,final' \
               -l 'branch' \
+              -l 'upload-address:' \
               -l 'no-upload,no-update' \
-              -l 'verbose,debug' \
+              -l 'quiet,verbose,debug' \
+              -l 'porcelain' \
               -l 'local-user:' \
               -l 'reviewer:' \
               -l 'force' \
@@ -105,6 +114,11 @@ while true; do
         warn_branch=true
         shift
         ;;
+    --upload-address )
+        shift
+        upload_address="$1"
+        shift
+        ;;
     --no-upload )
         do_upload=false
         shift
@@ -113,7 +127,13 @@ while true; do
         do_update=false
         shift
         ;;
+    --quiet )
+        ECHO=:
+        VERBOSE=:
+        shift
+        ;;
     --verbose )
+        ECHO=echo
         VERBOSE=echo
         git_quiet=
         shift
@@ -134,6 +154,10 @@ while true; do
         shift
         shift
         ;;
+    --porcelain )
+        do_porcelain=true
+        shift
+        ;;
     --force )
         force=true
         shift
@@ -195,14 +219,12 @@ trap "exec 42>&-; rm $VERBOSITY_FIFO" 0 2
 
 # Setup ##############################################################
 
-# Make sure we're in the work directory
-cd $(dirname $0)/..
-HERE=$(pwd)
-
 # Check that we have the scripts that define functions we use
+RELEASE_AUX=$(cd $(dirname $0)/release-aux; pwd)
 found=true
-for fn in "$HERE/dev/release-aux/release-version-fn.sh" \
-          "$HERE/dev/release-aux/release-state-fn.sh"; do
+for fn in "$RELEASE_AUX/release-version-fn.sh" \
+          "$RELEASE_AUX/release-state-fn.sh" \
+          "$RELEASE_AUX/upload-fn.sh"; do
     if ! [ -f "$fn" ]; then
         echo >&2 "'$fn' is missing"
         found=false
@@ -213,8 +235,27 @@ if ! $found; then
 fi
 
 # Load version functions
-. $HERE/dev/release-aux/release-version-fn.sh
-. $HERE/dev/release-aux/release-state-fn.sh
+. $RELEASE_AUX/release-version-fn.sh
+. $RELEASE_AUX/release-state-fn.sh
+# Load upload backend functions
+. $RELEASE_AUX/upload-fn.sh
+
+# Make sure we're in the work directory, and remember it
+if HERE=$(git rev-parse --show-toplevel); then
+    :
+else
+    echo >&2 "Not in a git worktree"
+    exit 1
+fi
+
+# Make sure that it's a plausible OpenSSL work tree, by checking
+# that a version file is found
+get_version
+
+if [ -z "$VERSION_FILE" ]; then
+    echo >&2 "Couldn't find OpenSSL version data"
+    exit 1
+fi
 
 # Make sure it's a branch we recognise
 orig_branch=$(git rev-parse --abbrev-ref HEAD)
@@ -233,16 +274,75 @@ else
 fi
 orig_HEAD=$(git rev-parse HEAD)
 
-# Initialize #########################################################
+# Make sure that we have fixup scripts for all the files that need
+# to be modified for a release.  We trust this, because we're not
+# going to change versioning scheme in the middle of a release.
+save_IFS=$IFS
+IFS=';'
+found=true
+for fn in $RELEASE_FILES; do
+    for file in "$RELEASE_AUX/fixup-$fn-release.pl" \
+                "$RELEASE_AUX/fixup-$fn-postrelease.pl"; do
+        if ! [ -f "$file" ]; then
+            echo >&2 "'$file' is missing"
+            found=false
+        fi
+    done
+done
+IFS=$save_IFS
+if ! $found; then
+    exit 1
+fi
 
-echo "== Initializing work tree"
+# We turn upload_address into a few variables, which can be used
+# by backends that must understand a subset of the SFTP commands
+upload_directory=
+upload_backend=
+case "$upload_address" in
+    *:* )
+        # Something with a colon is interpreted as the typical SCP
+        # location.  We reinterpret that in our terms
+        upload_directory="${upload_address#*:}"
+        upload_address="${upload_address%%:*}"
+        upload_backend=sftp
+        ;;
+    *@* )
+        upload_backend=sftp
+        ;;
+    sftp://?*/* | sftp://?* )
+        # First, remove the URI scheme
+        upload_address="${upload_address#sftp://}"
+        # Now we know that we have a host, followed by a slash, followed by
+        # a directory spec.  If there is no slash, there's no directory.
+        upload_directory="${upload_address#*/}"
+        if [ "$upload_directory" = "$upload_address" ]; then
+            # There was nothing with a slash to remove, so no directory.
+            upload_directory=
+        fi
+        upload_address="${upload_address%%/*}"
+        upload_backend=sftp
+        ;;
+    sftp:* )
+        echo >&2 "Invalid upload address $upload_address"
+        exit 1
+        ;;
+    * )
+        if $do_upload && ! [ -d "$upload_address" ]; then
+           echo >&2 "Not an existing directory: $upload_address"
+           exit 1
+        fi
+        upload_backend=file
+        ;;
+esac
 
-get_version
+# Initialize #########################################################
+
+$ECHO "== Initializing work tree"
 
 # Generate a cloned directory name
 release_clone="$orig_branch-release-tmp"
 
-echo "== Work tree will be in $release_clone"
+$ECHO "== Work tree will be in $release_clone"
 
 # Make a clone in a subdirectory and move there
 if ! [ -d "$release_clone" ]; then
@@ -257,10 +357,12 @@ get_version
 # changes for the release, the update branch is where we make the post-
 # release changes
 update_branch="$orig_branch"
-release_branch="openssl-$SERIES"
+release_branch="$(std_branch_name)"
 
 # among others, we only create a release branch if the patch number is zero
-if [ "$update_branch" = "$release_branch" ] || [ $PATCH -ne 0 ]; then
+if [ "$update_branch" = "$release_branch" ] \
+       || [ -z "$PATCH" ] \
+       || [ $PATCH -ne 0 ]; then
     if $do_branch && $warn_branch; then
         echo >&2 "Warning! We're already in a release branch; --branch ignored"
     fi
@@ -319,7 +421,7 @@ next_release_state "$next_method"
 $VERBOSE "== Creating a local release branch: $tmp_release_branch"
 git checkout $git_quiet -b "$tmp_release_branch"
 
-echo "== Configuring OpenSSL for update and release.  This may take a bit of time"
+$ECHO "== Configuring OpenSSL for update and release.  This may take a bit of time"
 
 ./Configure cc >&42
 
@@ -329,12 +431,14 @@ make update >&42
 # As long as we're doing an alpha release, we can have symbols without specific
 # numbers assigned. In a beta or final release, all symbols MUST have an
 # assigned number.
-if [ "$next_method" != 'alpha' ]; then
+if [ "$next_method" != 'alpha' ] && grep -q '^renumber *:' Makefile; then
     make renumber >&42
 fi
-make update-fips-checksums >&42
+if grep -q '^update-fips-checksums *:' Makefile; then
+    make update-fips-checksums >&42
+fi
 
-if [ -n "$(git status --porcelain)" ]; then
+if [ -n "$(git status --porcelain --untracked-files=no --ignore-submodules=all)" ]; then
     $VERBOSE "== Committing updates"
     git add -u
     git commit $git_quiet -m $'make update\n\nRelease: yes'
@@ -353,25 +457,27 @@ fi
 # Write the version information we updated
 set_version
 
+release="$FULL_VERSION"
 if [ -n "$PRE_LABEL" ]; then
-    release="$VERSION$_PRE_RELEASE_TAG$_BUILD_METADATA"
     release_text="$SERIES$_BUILD_METADATA $PRE_LABEL $PRE_NUM"
     announce_template=openssl-announce-pre-release.tmpl
 else
-    release="$VERSION$_BUILD_METADATA"
     release_text="$release"
     announce_template=openssl-announce-release.tmpl
 fi
-tag="openssl-$release"
+tag="$(std_tag_name)"
 $VERBOSE "== Updated version information to $release"
 
 $VERBOSE "== Updating files with release date for $release : $RELEASE_DATE"
-for fixup in "$HERE/dev/release-aux"/fixup-*-release.pl; do
-    file="$(basename "$fixup" | sed -e 's|^fixup-||' -e 's|-release\.pl$||')"
-    $VERBOSE "> $file"
-    RELEASE="$release" RELEASE_TEXT="$release_text" RELEASE_DATE="$RELEASE_DATE" \
-        perl -pi $fixup $file
-done
+(
+    IFS=';'
+    for file in $RELEASE_FILES; do
+        fixup="$RELEASE_AUX/fixup-$(basename "$file")-release.pl"
+        $VERBOSE "> $file"
+        RELEASE="$release" RELEASE_TEXT="$release_text" RELEASE_DATE="$RELEASE_DATE" \
+               perl -pi $fixup $file
+    done
+)
 
 $VERBOSE "== Committing updates and tagging"
 git add -u
@@ -379,20 +485,26 @@ git commit $git_quiet -m "Prepare for release of $release_text"$'\n\nRelease: ye
 if [ -n "$reviewers" ]; then
     addrev --release --nopr $reviewers
 fi
-echo "Tagging release with tag $tag.  You may need to enter a pass phrase"
+$ECHO "Tagging release with tag $tag.  You may need to enter a pass phrase"
 git tag$tagkey "$tag" -m "OpenSSL $release release tag"
 
 tarfile=openssl-$release.tar
 tgzfile=$tarfile.gz
 announce=openssl-$release.txt
 
-echo "== Generating tar, hash and announcement files.  This make take a bit of time"
+$ECHO "== Generating tar, hash and announcement files.  This make take a bit of time"
 
 $VERBOSE "== Making tarfile: $tgzfile"
-# Unfortunately, util/mktar.sh does verbose output on STDERR...  for good
-# reason, but it means we don't display errors unless --verbose
-./util/mktar.sh --tarfile="../$tarfile" 2>&1 \
-    | while read L; do $VERBOSE "> $L"; done
+
+# Unfortunately, some tarball generators do verbose output on STDERR...  for
+# good reason, but it means we don't display errors unless --verbose
+(
+    if [ -f ./util/mktar.sh ]; then
+        ./util/mktar.sh --tarfile="../$tarfile" 2>&1
+    else
+        make DISTTARVARS=TARFILE="../$tarfile" dist 2>&1
+    fi
+) | while read L; do $VERBOSE "> $L"; done
 
 if ! [ -f "../$tgzfile" ]; then
     echo >&2 "Where did the tarball end up? (../$tgzfile)"
@@ -410,7 +522,7 @@ sha256hash=$(cat "../$tgzfile.sha256")
 
 $VERBOSE "== Generating announcement text: $announce"
 # Hack the announcement template
-cat "$HERE/dev/release-aux/$announce_template" \
+cat "$RELEASE_AUX/$announce_template" \
     | sed -e "s|\\\$release_text|$release_text|g" \
           -e "s|\\\$release|$release|g" \
           -e "s|\\\$series|$SERIES|g" \
@@ -419,12 +531,12 @@ cat "$HERE/dev/release-aux/$announce_template" \
           -e "s|\\\$length|$length|" \
           -e "s|\\\$sha1hash|$sha1hash|" \
           -e "s|\\\$sha256hash|$sha256hash|" \
-    | perl -p "$HERE/dev/release-aux/fix-title.pl" \
+    | perl -p "$RELEASE_AUX/fix-title.pl" \
     > "../$announce"
 
 $VERBOSE "== Generating signatures: $tgzfile.asc $announce.asc"
 rm -f "../$tgzfile.asc" "../$announce.asc"
-echo "Signing the release files.  You may need to enter a pass phrase"
+$ECHO "Signing the release files.  You may need to enter a pass phrase"
 gpg$gpgkey --use-agent -sba "../$tgzfile"
 gpg$gpgkey --use-agent -sta --clearsign "../$announce"
 
@@ -433,21 +545,37 @@ $VERBOSE "== Push what we have to the parent repository"
 git push --follow-tags parent HEAD
 
 if $do_upload; then
-    (
-        if [ "$VERBOSE" != ':' ]; then
-            echo "progress"
-        fi
-        echo "put ../$tgzfile"
-        echo "put ../$tgzfile.sha1"
-        echo "put ../$tgzfile.sha256"
-        echo "put ../$tgzfile.asc"
-        echo "put ../$announce.asc"
-    ) \
-    | sftp "$upload_address"
+    $ECHO "== Upload tar, hash and announcement files"
 fi
 
+(
+    # With sftp, the progress meter is enabled by default,
+    # so we turn it off unless --verbose was given
+    if [ "$VERBOSE" == ':' ]; then
+        echo "progress"
+    fi
+    if [ -n "$upload_directory" ]; then
+        echo "cd $upload_directory"
+    fi
+    echo "put ../$tgzfile"
+    echo "put ../$tgzfile.sha1"
+    echo "put ../$tgzfile.sha256"
+    echo "put ../$tgzfile.asc"
+    echo "put ../$announce.asc"
+) | upload_backend_$upload_backend "$upload_address" $do_upload
+
 # Post-release #######################################################
 
+# Reset the files to their pre-release contents.  This doesn't affect
+# HEAD, but simply set all the files in a state that 'git revert -n HEAD'
+# would have given, but without the artifacts that 'git revert' adds.
+#
+# This allows all the post-release fixup scripts to perform from the
+# same point as the release fixup scripts, hopefully making them easier
+# to write.  This also makes the same post-release fixup scripts easier
+# to run when --branch has been used, as they will be run both on the
+# release branch and on the update branch, essentially from the same
+# state for affected files.
 $VERBOSE "== Reset all files to their pre-release contents"
 git reset $git_quiet HEAD^ -- .
 git checkout -- .
@@ -458,7 +586,7 @@ prev_release_date="$RELEASE_DATE"
 next_release_state "$next_method2"
 set_version
 
-release="$VERSION$_PRE_RELEASE_TAG$_BUILD_METADATA"
+release="$FULL_VERSION"
 release_text="$VERSION$_BUILD_METADATA"
 if [ -n "$PRE_LABEL" ]; then
     release_text="$SERIES$_BUILD_METADATA $PRE_LABEL $PRE_NUM"
@@ -466,14 +594,17 @@ fi
 $VERBOSE "== Updated version information to $release"
 
 $VERBOSE "== Updating files for $release :"
-for fixup in "$HERE/dev/release-aux"/fixup-*-postrelease.pl; do
-    file="$(basename "$fixup" | sed -e 's|^fixup-||' -e 's|-postrelease\.pl$||')"
-    $VERBOSE "> $file"
-    RELEASE="$release" RELEASE_TEXT="$release_text" \
-        PREV_RELEASE_TEXT="$prev_release_text" \
-        PREV_RELEASE_DATE="$prev_release_date" \
-        perl -pi $fixup $file
-done
+(
+    IFS=';'
+    for file in $RELEASE_FILES; do
+        fixup="$RELEASE_AUX/fixup-$(basename "$file")-postrelease.pl"
+        $VERBOSE "> $file"
+        RELEASE="$release" RELEASE_TEXT="$release_text" \
+               PREV_RELEASE_TEXT="$prev_release_text" \
+               PREV_RELEASE_DATE="$prev_release_date" \
+               perl -pi $fixup $file
+    done
+)
 
 $VERBOSE "== Committing updates"
 git add -u
@@ -494,17 +625,20 @@ if $do_branch; then
     next_release_state "minor"
     set_version
 
-    release="$VERSION$_PRE_RELEASE_TAG$_BUILD_METADATA"
+    release="$FULL_VERSION"
     release_text="$SERIES$_BUILD_METADATA"
     $VERBOSE "== Updated version information to $release"
 
     $VERBOSE "== Updating files for $release :"
-    for fixup in "$HERE/dev/release-aux"/fixup-*-postrelease.pl; do
-        file="$(basename "$fixup" | sed -e 's|^fixup-||' -e 's|-postrelease\.pl$||')"
-        $VERBOSE "> $file"
-        RELEASE="$release" RELEASE_TEXT="$release_text" \
-            perl -pi $fixup $file
-    done
+    (
+        IFS=';'
+        for file in $RELEASE_FILES; do
+            fixup="$RELEASE_AUX/fixup-$(basename "$file")-postrelease.pl"
+            $VERBOSE "> $file"
+            RELEASE="$release" RELEASE_TEXT="$release_text" \
+                   perl -pi $fixup $file
+        done
+    )
 
     $VERBOSE "== Committing updates"
     git add -u
@@ -514,16 +648,28 @@ if $do_branch; then
     fi
 fi
 
-# Push everything to the parent repo
-$VERBOSE "== Push what we have to the parent repository"
-git push parent HEAD
+if ! $clean_worktree; then
+    # Push everything to the parent repo
+    $VERBOSE "== Push what we have to the parent repository"
+    git push parent HEAD
+fi
 
 # Done ###############################################################
 
 $VERBOSE "== Done"
 
 cd $HERE
-cat <<EOF
+if $do_porcelain; then
+    echo "clone_directory='$release_clone'"
+    echo "update_branch='$tmp_update_branch'"
+    echo "final_update_branch='$update_branch'"
+    if [ "$tmp_release_branch" != "$tmp_update_branch" ]; then
+        echo "release_branch='$tmp_release_branch'"
+        echo "final_release_branch='$release_branch'"
+    fi
+    echo "release_tag='$tag'"
+else
+    cat <<EOF
 
 ======================================================================
 The release is done, and involves a few files and commits for you to
@@ -533,8 +679,8 @@ please see instructions that follow.
 
 EOF
 
-if $do_release; then
-    cat <<EOF
+    if $do_release; then
+        cat <<EOF
 
 The following files were uploaded to $upload_address, please ensure they
 are dealt with appropriately:
@@ -545,15 +691,15 @@ are dealt with appropriately:
     $tgzfile.asc
     $announce.asc
 EOF
-fi
+    fi
 
-cat <<EOF
+    cat <<EOF
 
 ----------------------------------------------------------------------
 EOF
 
-if $do_branch; then
-    cat <<EOF
+    if $do_branch; then
+        cat <<EOF
 You need to prepare the main repository with a new branch, '$release_branch'.
 That is done directly in the server's bare repository like this:
 
@@ -574,8 +720,8 @@ When merging them into the main repository, do it like this:
     git push git@github.openssl.org:openssl/openssl.git \\
         $tag
 EOF
-else
-cat <<EOF
+    else
+        cat <<EOF
 One additional release branch has been added to your repository.
 Push it to github, make a PR from it and have it approved:
 
@@ -588,14 +734,14 @@ When merging it into the main repository, do it like this:
     git push git@github.openssl.org:openssl/openssl.git \\
         $tag
 EOF
-fi
+    fi
 
-cat <<EOF
+    cat <<EOF
 
 ----------------------------------------------------------------------
 EOF
 
-cat <<EOF
+    cat <<EOF
 
 When everything is done, or if something went wrong and you want to start
 over, simply clean away temporary things left behind:
@@ -605,21 +751,22 @@ The release worktree:
     rm -rf $release_clone
 EOF
 
-if $do_branch; then
-    cat <<EOF
+    if $do_branch; then
+        cat <<EOF
 
 The additional release branches:
 
     git branch -D $tmp_release_branch
     git branch -D $tmp_update_branch
 EOF
-else
-    cat <<EOF
+    else
+        cat <<EOF
 
 The temporary release branch:
 
     git branch -D $tmp_release_branch
 EOF
+    fi
 fi
 
 exit 0
@@ -645,10 +792,13 @@ B<--final> |
 B<--branch> |
 B<--local-user>=I<keyid> |
 B<--reviewer>=I<id> |
+B<--upload-address>=I<address> |
 B<--no-upload> |
 B<--no-update> |
+B<--quiet> |
 B<--verbose> |
 B<--debug> |
+B<--porcelain> |
 B<--help> |
 B<--manual>
 ]
@@ -702,14 +852,43 @@ Create a branch specific for the I<SERIES> release series, if it doesn't
 already exist, and switch to it.  The exact branch name will be
 C<< openssl-I<SERIES> >>.
 
+=item B<--upload-address>=I<address>
+
+The location that the release files are to be uploaded to.  Supported values
+are:
+
+=over 4
+
+=item -
+
+an existing local directory
+
+=item -
+
+something that can be interpreted as an SCP/SFTP address.  In this case,
+SFTP will always be used.  Typical SCP remote file specs will be translated
+into something that makes sense for SFTP.
+
+=back
+
+The default upload address is C<upload@dev.openssl.org>.
+
 =item B<--no-upload>
 
-Don't upload the produced files.
+Don't upload the release files.
 
 =item B<--no-update>
 
 Don't run C<make update> and C<make update-fips-checksums>.
 
+=item B<--quiet>
+
+Really quiet, only bare necessity output, which is the final instructions,
+or should the B<--porcelain> option be used, only that output.
+
+messages appearing on standard error will still be shown, but should be
+fairly minimal.
+
 =item B<--verbose>
 
 Verbose output.
@@ -718,6 +897,43 @@ Verbose output.
 
 Display extra debug output.  Implies B<--no-upload>
 
+=item B<--porcelain>
+
+Give final output in an easy-to-parse format for scripts.  The output comes
+in a form reminicent of shell variable assignments.  Currently supported are:
+
+=over 4
+
+=item B<clone_directory>=I<dir>
+
+The directory for the clone that this script creates.
+
+=item B<update_branch>=I<branch>
+
+The temporary update branch
+
+=item B<final_update_branch>=I<branch>
+
+The final update branch that the temporary update branch should end up being
+merged into.
+
+=item B<release_branch>=I<branch>
+
+The temporary release branch, only given if it differs from the update branch
+(i.e. B<--branch> was given or implied).
+
+=item B<final_release_branch>=I<branch>
+
+The final release branch that the temporary release branch should end up being
+merged into.  This is only given if it differs from the final update branch
+(i.e. B<--branch> was given or implied).
+
+=item B<release_tag>=I<tag>
+
+The release tag.
+
+=back
+
 =item B<--local-user>=I<keyid>
 
 Use I<keyid> as the local user for C<git tag> and for signing with C<gpg>.