Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions scripts/release-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
#
# Licensed to the Technische Universität Darmstadt under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The Technische Universität Darmstadt
# 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.
#
# 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.

# Runs the release steps in order, stopping at the first failure.
#
# Re-runnable: every step decides for itself whether there is anything to do -
# maven refuses on a dirty tree, macos/docker rebuild in place, windows reuses a
# successful CI run, draft leaves an existing draft alone, upload skips assets
# already attached. So after fixing whatever broke, run this again and it carries
# on rather than starting over.
#
# The release is deliberately left as a draft: review the notes and publish in
# the browser. The remaining manual steps are listed by `release.sh status`.
#
# Usage: release-all.sh [--from <step>] [--dry-run]

set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=release-functions.shlib
source "$SCRIPT_DIR/release-functions.shlib"

# maven first (it creates target/checkout that the rest read the version from),
# then the builds, then windows - which waits on CI and is by far the slowest -
# then the release itself. upload comes last so it sees every artifact.
STEPS="maven macos windows docker draft upload"

from=''
dry_run=0
while [ $# -gt 0 ]; do
case "$1" in
-h|--help|help)
info "Usage: release-all.sh [--from <step>] [--dry-run]"
info "Steps: $STEPS"
exit 0 ;;
--dry-run) dry_run=1 ;;
--from)
shift; [ $# -gt 0 ] || die "--from needs a step name."
from="$1" ;;
--from=*) from="${1#*=}" ;;
*) die "Unexpected argument '$1'." ;;
esac
shift
done

if [ -n "$from" ]; then
case " $STEPS " in
*" $from "*) ;;
*) die "Unknown step '$from'. Steps: $STEPS" ;;
esac
fi

dry_args=()
if [ "$dry_run" -eq 1 ]; then
dry_args=(--dry-run)
# Everything after maven reads the version from target/checkout, which only
# exists once release:perform has run. Without it a dry run can only show the
# first step, so say that instead of failing four times over.
if ! version_from_checkout >/dev/null 2>&1; then
info "${C_YELLOW}No target/checkout yet${C_RESET} - the steps after maven derive the"
info "version from it, so they cannot be previewed before the release has run."
info ""
info "Steps that would run, in order:"
for step in $STEPS; do info " $step"; done
exit 0
fi
fi

skipping=0
[ -n "$from" ] && skipping=1

for step in $STEPS; do
if [ "$skipping" -eq 1 ]; then
if [ "$step" = "$from" ]; then
skipping=0
else
info "${C_DIM}--- skip $step${C_RESET}"
continue
fi
fi

info ""
info "${C_BOLD}=== $step ===${C_RESET}"
info ""

# Not exec'd and not backgrounded: each step's output goes straight to the
# terminal, and a failure stops the sequence here so the log ends at the
# problem rather than scrolling past it.
if ! "$SCRIPT_DIR/release-$step.sh" "${dry_args[@]}"; then
info ""
die "Step '$step' failed. Fix it, then resume with:
release.sh all --from $step"
fi
done

info ""
info "${C_BOLD}=== done ===${C_RESET}"
info ""
if [ "$dry_run" -eq 1 ]; then
info "${C_DIM}dry run - nothing was executed${C_RESET}"
exit 0
fi
info "The release is still a draft. Review the notes and publish it in the browser."
info "Outstanding work: release.sh status"
84 changes: 84 additions & 0 deletions scripts/release-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
#
# Licensed to the Technische Universität Darmstadt under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The Technische Universität Darmstadt
# 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.
#
# 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.

# Builds and pushes the multi-arch Docker image from target/checkout.
#
# Usage: release-docker.sh [--dry-run]

set -euo pipefail

# shellcheck source=release-functions.shlib
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/release-functions.shlib"

dry_run=0
while [ $# -gt 0 ]; do
case "$1" in
-h|--help|help) info "Usage: release-docker.sh [--dry-run]"; exit 0 ;;
--dry-run) dry_run=1 ;;
*) die "Unexpected argument '$1'." ;;
esac
shift
done

# Build from target/checkout, not from the working tree: the image tag comes from
# ${project.version}, so building here would push <version>-SNAPSHOT from the
# development poms. The checkout is the tag release:perform checked out, which is
# what carries the released version - so the version is not a parameter, it is
# whatever that tree says it is.
build_dir="$PROJECT_ROOT/target/checkout"
[ -d "$build_dir" ] || die "\
No target/checkout - run the Maven release first (release.sh maven).
The Docker image is built from the checked-out release tag, not the working tree."
version="$(version_from_checkout)" \
|| die "target/checkout has no released version (a SNAPSHOT, or an aborted run?)."

# The image name has to be passed explicitly: the dist-docker profile defaults it
# to inception-snapshots and release-docker does not override it, so omitting it
# would push a release to the snapshots repository. Betas belong there, final
# releases do not - image_repo_for() encodes that.
image_repo="$(image_repo_for "$version")"
warn_if_checkout_published "$version"

MVN_ARGS=(-pl :inception-dist-docker -Pdist-docker,release-docker clean deploy
"-Ddocker.image.name=$image_repo")

info " cd target/checkout && mvn ${MVN_ARGS[*]}"
info ""
info "Pushing ${C_BOLD}$image_repo:$version${C_RESET}"
if is_beta_version "$version"; then
info "${C_DIM}beta - goes to the snapshots repository${C_RESET}"
fi
info ""

if [ "$dry_run" -eq 1 ]; then
info "${C_DIM}dry run - nothing executed${C_RESET}"
exit 0
fi

require_tool mvn
require_tool docker
# Warn early rather than after the multi-arch build. Only checks that ghcr.io is
# known to Docker at all; whether the credentials are still valid shows at push.
grep -q '"ghcr.io"' "${DOCKER_CONFIG:-$HOME/.docker}/config.json" 2>/dev/null \
|| info "${C_YELLOW}ghcr.io not found in the Docker config - run: docker login ghcr.io${C_RESET}"

cd "$build_dir"
mvn "${MVN_ARGS[@]}"

info ""
info "Now check: release.sh status $version"
178 changes: 178 additions & 0 deletions scripts/release-draft.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#!/usr/bin/env bash
#
# Licensed to the Technische Universität Darmstadt under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The Technische Universität Darmstadt
# 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.
#
# 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.

# Creates the GitHub release as a draft, with the changelog and the usual
# boilerplate, and attaches the built artifacts.
#
# A draft on purpose: review and edit it in the browser before publishing. The
# issue-type emojis past releases carry are not reproduced - GitHub's generated
# changelog has no way to know them - so add those while editing if wanted.
#
# Usage: release-draft.sh [version] [--dry-run]

set -euo pipefail

# shellcheck source=release-functions.shlib
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/release-functions.shlib"

version=''
dry_run=0
while [ $# -gt 0 ]; do
case "$1" in
-h|--help|help) info "Usage: release-draft.sh [version] [--dry-run]"; exit 0 ;;
--dry-run) dry_run=1 ;;
-*) die "Unknown option '$1'." ;;
*) [ -z "$version" ] || die "Unexpected argument '$1'."; version="$1" ;;
esac
shift
done

[ -n "$version" ] || version="$(version_from_checkout)" \
|| die "No version given and none could be derived from target/checkout."

tag="$(tag_for "$version")"

# The release this one upgrades from, and what the changelog is diffed against.
#
# 'inception-[0-9]*' rather than 'inception-*': the latter also matches the old
# inception-app-* tags, which sort above everything and would produce a changelog
# spanning years.
#
# Betas are skipped for a final release: 41.0's notes say "from 40.6 to 41.0",
# not from 41.0-beta-2, even though the beta sorts higher. For a beta itself the
# preceding beta is the right comparison, so they are kept in that case.
# Picks the greatest tag strictly below this version rather than simply the
# newest one, so re-drafting an older release still compares against the right
# predecessor instead of the latest tag in the repository.
previous_tag() {
local tags
tags="$(git -C "$PROJECT_ROOT" tag --list 'inception-[0-9]*' --sort=-v:refname \
| grep -vFx "$tag")"
if is_beta_version "$version"; then
# sort -V treats 41.0 as *lower* than 41.0-beta-1, so the final release of
# the same base would be picked as a beta's predecessor. Drop it.
tags="$(grep -vFx "$(tag_for "$(base_version_for "$version")")" <<<"$tags" || true)"
else
tags="$(grep -v -- '-beta' <<<"$tags" || true)"
fi
# sort -V puts our tag among the others; whatever lands just after it is the
# next lower version.
printf '%s\n%s\n' "$tags" "$tag" | sort -Vr | grep -A1 -Fx "$tag" | tail -1
}

# A .0 release carries features; anything else is bugfixes only.
release_kind() {
case "$version" in
*-beta*) printf 'This is a beta release.\n' ;;
*.0) printf 'This is a feature and bug fix release.\n' ;;
*) printf 'This is a bug fix release.\n' ;;
esac
}

java_release() {
sed -n 's|.*<maven.compiler.release>\([0-9]*\)</maven.compiler.release>.*|\1|p' \
"$PROJECT_ROOT/inception/pom.xml" | head -1
}

prev="$(previous_tag)"
prev_version="${prev#inception-}"
java_ver="$(java_release)"
[ -n "$java_ver" ] || die "Could not read maven.compiler.release from inception/pom.xml."

# GitHub generates the same "## What's Changed" + "Full Changelog" block that
# past releases carry, so it is fetched rather than rebuilt from the git log.
changelog() {
gh api "repos/$REPO/releases/generate-notes" \
-f tag_name="$tag" -f previous_tag_name="$prev" --jq '.body' 2>/dev/null \
|| info "(changelog generation failed - fill this in manually)"
}

TEMPLATE="$SCRIPT_DIR/release-notes.md.template"

# Renders the template, replacing {{NAME}} placeholders.
#
# Placeholders rather than a sourced heredoc: the template is prose that gets
# edited as text, and a heredoc would evaluate any $ or backtick it happens to
# contain. Multi-line values are substituted with awk, since sed cannot insert
# newlines portably.
body() {
[ -f "$TEMPLATE" ] || die "Template not found: $TEMPLATE"

# The changelog is many lines, and BSD awk rejects newlines in -v values, so
# it is passed as a file and spliced in line by line.
local changes_file; changes_file="$(mktemp)"
# shellcheck disable=SC2064
trap "rm -f '$changes_file'" RETURN
changelog >"$changes_file"

awk -v kind="$(release_kind)" -v changes_file="$changes_file" \
-v version="$version" -v prev="$prev_version" -v java="$java_ver" '
{
gsub(/\{\{RELEASE_KIND\}\}/, kind)
gsub(/\{\{PREVIOUS_VERSION\}\}/, prev)
gsub(/\{\{JAVA_VERSION\}\}/, java)
gsub(/\{\{VERSION\}\}/, version)
if ($0 == "{{CHANGELOG}}") {
while ((getline line < changes_file) > 0) print line
next
}
print
}
' "$TEMPLATE"
}

# Artifacts are not attached here - that is `release.sh upload`, which can run
# repeatedly as they appear (the Windows MSI comes last, from CI). Keeping the
# two apart means the build steps and this one can run in any order.
info "Draft ${C_BOLD}INCEpTION $version${C_RESET} ($tag), changelog since $prev"
info ""

if [ "$dry_run" -eq 1 ]; then
info "${C_DIM}dry run - the body that would be used:${C_RESET}"
info ""
body
exit 0
fi

require_tool gh
require_tool jq
git -C "$PROJECT_ROOT" rev-parse -q --verify "refs/tags/$tag" >/dev/null 2>&1 \
|| die "Tag $tag does not exist - run the Maven release first."

# An existing draft is left alone rather than treated as an error: notes may have
# been edited by hand already, and re-running must not discard that. This is what
# makes `release.sh all` resumable. A published release is a different matter.
if existing="$(gh release view "$tag" --repo "$REPO" --json isDraft,url 2>/dev/null)"; then
if [ "$(jq -r '.isDraft' <<<"$existing")" = "true" ]; then
info "A draft for $tag already exists - leaving it untouched."
info "$(jq -r '.url' <<<"$existing")"
info "Attach the artifacts with: release.sh upload $version"
exit 0
fi
die "Release $tag is already published - not replacing it."
fi

prerelease=()
is_beta_version "$version" && prerelease=(--prerelease)

gh release create "$tag" --repo "$REPO" --draft --verify-tag \
--title "INCEpTION $version" --notes-file - "${prerelease[@]}" < <(body)

info ""
info "Draft created. Attach the artifacts with: release.sh upload $version"
gh release view "$tag" --repo "$REPO" --json url --jq '.url'
Loading
Loading