diff --git a/.github/actions/release-prep-only/action.yml b/.github/actions/release-prep-only/action.yml new file mode 100644 index 0000000000..cf8b59a367 --- /dev/null +++ b/.github/actions/release-prep-only/action.yml @@ -0,0 +1,91 @@ +name: Release-prep-only changes +description: >- + Detects whether this PR or push contains only release-preparation changes: + the top-level LICENSE.txt file, and/or pure akka-http version bumps in + sample build files (samples/**/build.sbt, samples/**/pom.xml, + samples/**/build.gradle). Sets release_prep_only="true" only when every + changed file matches the whitelist AND, for sample build files, the only + diff is the akka-http version literal. Any other change (a different + dependency, a non-version edit, a README, a sample source file) results + in "false" so the regular CI runs. Requires the repository to be checked + out with sufficient history to diff the base and head shas (typically + fetch-depth: 0). +outputs: + release_prep_only: + description: '"true" if change is limited to LICENSE.txt and/or akka-http version bumps in sample build files' + value: ${{ steps.filter.outputs.release_prep_only }} +runs: + using: composite + steps: + - name: Detect release-prep-only changes + id: filter + shell: bash + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + set -euo pipefail + if [ -z "${BASE_SHA:-}" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then + echo "No usable base sha; assuming non release-prep-only" + echo "release_prep_only=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + changed=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA") + echo "Changed files between $BASE_SHA and $HEAD_SHA:" + echo "$changed" + + if [ -z "$changed" ]; then + echo "No changed files; treating as non release-prep" + echo "release_prep_only=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # For each sample build file, normalize the akka-http version pattern out + # and compare base vs head. If the normalized contents differ, the change + # is not a pure version bump and full CI should run. + result=true + while IFS= read -r f; do + [ -z "$f" ] && continue + + if [ "$f" = "LICENSE.txt" ]; then + continue + fi + + if [[ "$f" =~ ^samples/.+/build\.sbt$ ]]; then + # Sample build.sbt uses the override-aware + # 'lazy val akkaHttpVersion = sys.props.getOrElse("akka-http.version", "...")' + sed_args=( + -e 's/^lazy val akkaHttpVersion = sys\.props\.getOrElse\("akka-http\.version", "[^"]*"\)$/lazy val akkaHttpVersion = sys.props.getOrElse("akka-http.version", "X")/' + ) + elif [[ "$f" =~ ^samples/.+/pom\.xml$ ]]; then + sed_args=(-e 's|^([[:space:]]*)[^<]*$|\1X|') + elif [[ "$f" =~ ^samples/.+/build\.gradle$ ]]; then + sed_args=(-e "s|('com\\.typesafe\\.akka:[^:']+:)[^']*'|\\1X'|g") + else + echo "Non-release-prep file changed: $f" + result=false + break + fi + + if ! base=$(git show "$BASE_SHA:$f" 2>/dev/null); then + echo "$f did not exist at base; treating as non release-prep" + result=false + break + fi + if ! head=$(git show "$HEAD_SHA:$f" 2>/dev/null); then + echo "$f deleted at head; treating as non release-prep" + result=false + break + fi + + base_norm=$(echo "$base" | sed -E "${sed_args[@]}") + head_norm=$(echo "$head" | sed -E "${sed_args[@]}") + if [ "$base_norm" != "$head_norm" ]; then + echo "Change in $f is not limited to akka-http version bump" + result=false + break + fi + done <<< "$changed" + + echo "release_prep_only=$result" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/check-samples.yml b/.github/workflows/check-samples.yml index 09a5736952..86d58408fa 100644 --- a/.github/workflows/check-samples.yml +++ b/.github/workflows/check-samples.yml @@ -15,8 +15,24 @@ permissions: contents: read jobs: + changes: + name: Detect changes + runs-on: Akka-Default + outputs: + release_prep_only: ${{ steps.filter.outputs.release_prep_only }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Detect release-prep-only changes + id: filter + uses: ./.github/actions/release-prep-only + check-samples: name: Check Sample Projects + needs: changes + if: needs.changes.outputs.release_prep_only != 'true' runs-on: Akka-Default steps: - name: Checkout Global Scripts diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 867aef3da5..f21b637a5a 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -15,10 +15,27 @@ permissions: contents: read jobs: + changes: + name: Detect changes + runs-on: Akka-Default + outputs: + release_prep_only: ${{ steps.filter.outputs.release_prep_only }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Detect release-prep-only changes + id: filter + uses: ./.github/actions/release-prep-only + documentation: name: Documentation + needs: changes runs-on: Akka-Default - if: github.repository == 'akka/akka-http' + if: | + github.repository == 'akka/akka-http' && + needs.changes.outputs.release_prep_only != 'true' steps: - name: Checkout Global Scripts uses: actions/checkout@v6 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 39475a5d5f..e22067d142 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,10 +9,27 @@ permissions: contents: read jobs: + changes: + name: Detect changes + runs-on: Akka-Default + outputs: + release_prep_only: ${{ steps.filter.outputs.release_prep_only }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Detect release-prep-only changes + id: filter + uses: ./.github/actions/release-prep-only + publish-artifacts: name: Publish artifacts + needs: changes runs-on: Akka-Default - if: github.event.repository.fork == false + if: | + github.event.repository.fork == false && + needs.changes.outputs.release_prep_only != 'true' steps: - name: Checkout Global Scripts uses: actions/checkout@v6 diff --git a/.github/workflows/validate-and-test.yml b/.github/workflows/validate-and-test.yml index 3a6c6dbc62..e5702d666c 100644 --- a/.github/workflows/validate-and-test.yml +++ b/.github/workflows/validate-and-test.yml @@ -15,8 +15,24 @@ concurrency: cancel-in-progress: true jobs: + changes: + name: Detect changes + runs-on: Akka-Default + outputs: + release_prep_only: ${{ steps.filter.outputs.release_prep_only }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Detect release-prep-only changes + id: filter + uses: ./.github/actions/release-prep-only + formatting-check: name: Checks + needs: changes + if: needs.changes.outputs.release_prep_only != 'true' runs-on: Akka-Default steps: - name: Checkout Global Scripts @@ -59,6 +75,8 @@ jobs: compile-and-test: name: Compile and test + needs: changes + if: needs.changes.outputs.release_prep_only != 'true' runs-on: Akka-Default strategy: fail-fast: false diff --git a/docs/release-train-issue-template.md b/docs/release-train-issue-template.md index deeafc2195..02a39f8521 100644 --- a/docs/release-train-issue-template.md +++ b/docs/release-train-issue-template.md @@ -16,10 +16,12 @@ Key links: ### Cutting the release -- [ ] Check that open PRs and issues assigned to the milestone are reasonable +- [ ] Check that [open PRs](https://github.com/akka/akka-http/pulls) and [issues assigned to the milestone](https://github.com/akka/akka-http/issues?q=milestone%3A%22$VERSION$%22) are reasonable - [ ] If PRs were merged after EU midnight, trigger the [native-image tests](https://github.com/akka/akka-http/actions/workflows/native-image-tests.yml) and see that they are gr -- [ ] Update the Change date in the LICENSE file. -- [ ] Update the Akka HTTP version in the samples to $VERSION$, otherwise the published zip files of the samples will have the old version. +- [ ] Run the release-prep script to update LICENSE.txt (version, copyright year, change date + 3y) and bump the akka-http version in every sample build file. With `--commit-and-pr` it pushes a `wip-release-prep-$VERSION$` branch and opens a PR: + ``` + ./scripts/release-prep.sh --commit-and-pr $VERSION$ + ``` - [ ] For minor or major versions, add a release notes entry in `docs/src/main/paradox/release-notes/`. - [ ] Create a new milestone for the [next version](https://github.com/akka/akka-http/milestones) - [ ] Close the [$VERSION$ milestone](https://github.com/akka/akka-http/milestones?direction=asc&sort=due_date) @@ -27,7 +29,7 @@ Key links: - [ ] For recent dependency updates or changes on a minor release branch the Fossa validation can be triggered from the GitHub actions "Dependency License Scanning" (Manually choosing the release branch) - [ ] Update the revision in Fossa in the Akka Group for the Akka umbrella version, e.g. `22.10`. Note that the revisions for the release is udpated by Akka Group > Projects > Edit. For recent dependency updates the Fossa validation can be triggered from the GitHub actions "Dependency License Scanning". - [ ] Wait until [main build finished](https://github.com/akka/akka-http/actions) after merging the latest PR -- [ ] Update the [draft release](https://github.com/akka/akka-http/releases) with the next tag version `v$VERSION$`, title and release description. Use the `Publish release` button, which will create the tag. +- [ ] Create the [draft release](https://github.com/akka/akka-http/releases/new?tag=v$VERSION$), click `Generate release notes` to get a title and release description. Use the `Publish release` button, which will create the tag. - [ ] Check that GitHub Actions release build has executed successfully (GitHub Actions will start a [CI build](https://github.com/akka/akka-http/actions) for the new tag and publish artifacts to https://repo.akka.io/TOKEN/secure) ### Check availability diff --git a/scripts/release-prep.sh b/scripts/release-prep.sh new file mode 100755 index 0000000000..df809ce932 --- /dev/null +++ b/scripts/release-prep.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# +# release-prep.sh [--commit-and-pr] +# +# Prepares the repository for a release by: +# - Updating the LICENSE.txt file: +# * "Licensed Work: Akka HTTP X.Y.Z" -> the given akka-http version +# * "(c) YYYY Lightbend Inc." -> the current year +# * "Change Date: YYYY-MM-DD" -> today + 3 years +# - Bumping the akka-http version in every sample build file: +# * samples/**/build.sbt (sys.props.getOrElse("akka-http.version", ...)) +# * samples/**/pom.xml (...) +# * samples/**/build.gradle ('com.typesafe.akka::') +# +# By default the script only updates the files; review with `git diff` and +# commit yourself. With --commit-and-pr it creates a wip-release-prep-X.Y.Z +# branch off of the currently checked-out branch, applies the changes, +# commits, pushes, and opens a pull request via the gh CLI. + +set -euo pipefail + +usage() { + echo "Usage: $0 [--commit-and-pr] " >&2 + echo "Example: $0 10.7.5" >&2 + echo " $0 --commit-and-pr 10.7.5" >&2 +} + +COMMIT_AND_PR=false +POSITIONAL=() +while [ $# -gt 0 ]; do + case "$1" in + --commit-and-pr) COMMIT_AND_PR=true; shift ;; + -h|--help) usage; exit 0 ;; + --) shift; POSITIONAL+=("$@"); break ;; + -*) echo "Unknown flag: $1" >&2; usage; exit 1 ;; + *) POSITIONAL+=("$1"); shift ;; + esac +done +if [ ${#POSITIONAL[@]} -ne 1 ]; then + usage + exit 1 +fi + +AKKA_HTTP_VERSION="${POSITIONAL[0]}" +COMMIT_MSG="chore: License change date and sample bump for $AKKA_HTTP_VERSION" + +# Cross-platform date arithmetic: BSD (macOS) and GNU (Linux) take different flags. +if date -v+0d >/dev/null 2>&1; then + CHANGE_DATE=$(date -v+3y +%Y-%m-%d) +else + CHANGE_DATE=$(date -d '+3 years' +%Y-%m-%d) +fi +CURRENT_YEAR=$(date +%Y) + +# Cross-platform in-place sed: BSD requires an explicit backup-extension argument +# (empty = no backup), GNU does not accept the empty argument. Stored as an +# array so it composes with both direct calls and `find -exec`. +if sed --version >/dev/null 2>&1; then + SED_INPLACE=(sed -i) +else + SED_INPLACE=(sed -i '') +fi + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +# When opening a PR we need a clean tree, the gh CLI, and a free branch name. +if [ "$COMMIT_AND_PR" = true ]; then + command -v gh >/dev/null 2>&1 || { + echo "gh CLI not found; install from https://cli.github.com" >&2 + exit 1 + } + if ! git diff --quiet || ! git diff --cached --quiet; then + echo "Working tree is not clean; commit or stash changes first." >&2 + exit 1 + fi + TARGET_BASE=$(git rev-parse --abbrev-ref HEAD) + if [ "$TARGET_BASE" = "HEAD" ]; then + echo "Detached HEAD; check out the target branch (e.g. main or release-X.Y) first." >&2 + exit 1 + fi + NEW_BRANCH="wip-release-prep-$AKKA_HTTP_VERSION" + if git rev-parse --verify "$NEW_BRANCH" >/dev/null 2>&1; then + echo "Branch $NEW_BRANCH already exists; aborting." >&2 + exit 1 + fi + echo "Creating branch $NEW_BRANCH (PR will target $TARGET_BASE)" + git checkout -b "$NEW_BRANCH" + echo +fi + +echo "Preparing release of Akka HTTP $AKKA_HTTP_VERSION" +echo " Copyright year: $CURRENT_YEAR" +echo " Change Date: $CHANGE_DATE (today + 3 years)" +echo + +echo "Updating LICENSE.txt..." +"${SED_INPLACE[@]}" -E \ + -e "s/^(Licensed Work:[[:space:]]+Akka HTTP )[^[:space:]]+/\\1$AKKA_HTTP_VERSION/" \ + -e "s/(The Licensed Work is \\(c\\) )[0-9]{4}( Lightbend Inc\\.)/\\1$CURRENT_YEAR\\2/" \ + -e "s/^(Change Date:[[:space:]]+)[0-9-]+/\\1$CHANGE_DATE/" \ + LICENSE.txt + +echo "Bumping samples to $AKKA_HTTP_VERSION..." +# build.sbt: sys.props-aware `lazy val akkaHttpVersion = sys.props.getOrElse("akka-http.version", "...")` form. +find samples -name build.sbt -exec "${SED_INPLACE[@]}" -E \ + -e "s/^lazy val akkaHttpVersion = sys\\.props\\.getOrElse\\(\"akka-http\\.version\", \"[^\"]*\"\\)/lazy val akkaHttpVersion = sys.props.getOrElse(\"akka-http.version\", \"$AKKA_HTTP_VERSION\")/" \ + {} + + +# pom.xml: the property. +find samples -name pom.xml -exec "${SED_INPLACE[@]}" -E \ + -e "s|[^<]*|$AKKA_HTTP_VERSION|" \ + {} + + +# build.gradle: every 'com.typesafe.akka::' coordinate. +find samples -name build.gradle -exec "${SED_INPLACE[@]}" -E \ + -e "s|('com\\.typesafe\\.akka:[^:']+:)[^']*'|\\1$AKKA_HTTP_VERSION'|g" \ + {} + + +if [ "$COMMIT_AND_PR" = true ]; then + echo + echo "Committing..." + git add LICENSE.txt samples + git commit -m "$COMMIT_MSG" + echo "Pushing $NEW_BRANCH..." + git push -u origin "$NEW_BRANCH" + echo "Opening pull request..." + gh pr create --base "$TARGET_BASE" --title "$COMMIT_MSG" --body "$COMMIT_MSG" + echo "Done." +else + echo + echo "Done. Review the changes with 'git diff' and commit when ready." + echo "(Re-run with --commit-and-pr to commit, push, and open a PR automatically.)" +fi