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