You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2026/09/25 15:36:01 UTC
(grails-core) 01/01: Merge branch '7.2.x' into 8.0.x
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a commit to branch 8.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 0dab8a726376f819375a95f2a90c70ad6d19d00a
Merge: e4cde3c99b eddc4ee8dc
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Fri Sep 25 11:30:34 2026 -0400
Merge branch '7.2.x' into 8.0.x
# Conflicts:
# .github/workflows/codestyle.yml
# .github/workflows/gradle.yml
# .github/workflows/groovy-joint-workflow.yml
# .github/workflows/vulnerability-scan.yml
# AGENTS.md
# gradle.properties
# grails-core/src/test/groovy/grails/util/GrailsUtilTests.java
# grails-data-hibernate5/docs/build.gradle
# grails-doc/src/en/guide/upgrading/upgrading60x.adoc
# grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/view/GrailsGspSpec.groovy
# grails-gradle/model/src/main/groovy/org/grails/io/support/SpringIOUtils.java
# grails-gradle/model/src/test/groovy/org/grails/io/support/SpringIOUtilsSpec.groovy
# grails-interceptors/src/main/groovy/grails/artefact/Interceptor.groovy
# grails-interceptors/src/main/groovy/org/grails/plugins/web/interceptors/UrlMappingMatcher.groovy
# grails-interceptors/src/test/groovy/grails/artefact/InterceptorSpec.groovy
# grails-testing-support-http-client/src/main/groovy/org/apache/grails/testing/http/client/utils/XmlUtils.groovy
# grails-testing-support-http-client/src/test/groovy/org/apache/grails/testing/http/client/TestHttpResponseSpec.groovy
# grails-testing-support-http-client/src/test/groovy/org/apache/grails/testing/http/client/utils/XmlUtilsSpec.groovy
.github/workflows/benchmark.yml | 12 +-
.github/workflows/codeanalysis.yml | 12 +-
.github/workflows/codeql.yml | 4 +-
.github/workflows/codestyle.yml | 18 +--
.github/workflows/coverage.yml | 16 +--
.github/workflows/end-to-end.yml | 8 +-
.github/workflows/forge-deploy-aws.yml | 4 +-
.github/workflows/gradle.yml | 130 ++++++++++-----------
.github/workflows/groovy-snapshot-canary.yml | 18 +--
.github/workflows/rat.yml | 6 +-
.github/workflows/release-close.yml | 2 +-
.github/workflows/release-publish-docs.yml | 4 +-
.github/workflows/release-verify.yml | 4 +-
.github/workflows/release.yml | 20 ++--
.github/workflows/sitemesh2.yml | 8 +-
.github/workflows/validate-actions.yml | 4 +-
.github/workflows/vulnerability-scan.yml | 10 +-
AGENTS.md | 10 +-
.../grails/doc/asciidoc/AsciiDocEngine.groovy | 6 +-
.../grails/doc/asciidoc/AsciiDocEngineSpec.groovy | 85 ++++++++++++++
.../tests/ValidationErrorToOneReadOnlySpec.groovy | 107 +++++++++++++++++
grails-data-hibernate5/docs/build.gradle | 4 +
.../src/docs/asciidoc/databaseMigration/index.adoc | 2 +-
.../docs/src/docs/asciidoc/index.adoc | 1 +
.../docs/src/docs/asciidoc/persistenceBasics.adoc | 2 +-
.../docs/asciidoc/persistenceBasics/cascades.adoc | 5 +
.../docs/asciidoc/programmaticTransactions.adoc | 4 +-
.../docs/src/docs/asciidoc/querying.adoc | 4 +-
.../src/en/guide/testing/functionalTesting.adoc | 17 +++
.../org/grails/forge/rocker/plugin/RockerTask.java | 11 +-
.../grails/forge/api/PreviewControllerSpec.groovy | 2 +-
.../build/gradle/templates/buildGradle.rocker.raw | 10 +-
.../grails/forge/feature/other/GrailsQuartz.java | 2 +-
.../org/grails/forge/feature/test/GebFeature.java | 47 ++++++++
...iverBinaries.java => GebWithLocalBrowsers.java} | 56 +++------
.../forge/feature/test/GebWithTestcontainers.java | 30 ++---
...pock.rocker.raw => containerGebSpec.rocker.raw} | 0
.../{groovyJunit.rocker.raw => gebSpec.rocker.raw} | 32 ++---
.../template/webdriverBinariesPlugin.rocker.raw | 29 -----
.../grails-forge-core/src/main/resources/pom.xml | 10 --
.../forge/feature/other/GrailsQuartzSpec.groovy | 60 ++++++++++
...Spec.groovy => GebWithLocalBrowsersSpec.groovy} | 83 +++++++++----
.../feature/test/GebWithTestcontainersSpec.groovy | 25 ++++
.../features/geb/GebWithLocalBrowsersSpec.groovy | 43 +++++++
44 files changed, 671 insertions(+), 296 deletions(-)
diff --cc .github/workflows/benchmark.yml
index 68488b337a,0000000000..cf5ce77353
mode 100644,000000..100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@@ -1,394 -1,0 +1,394 @@@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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. You may obtain a copy of the License at
+#
+# https://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.
+
+name: "JMH Benchmark Comparison"
+
+# SECURITY: This workflow deliberately uses pull_request, never pull_request_target.
+# Pull requests run untrusted code, and Apache Infra policy forbids exposing tokens
+# to that code through a privileged pull_request_target workflow.
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, labeled]
+ paths-ignore:
+ - '**/*.md'
+ - '**/*.adoc'
+ - 'grails-doc/**'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}-${{ github.event.action == 'labeled' && github.event.label.name != 'performance' && github.run_id || 'benchmark' }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+permissions:
+ contents: read
+
+jobs:
+ # Each shard builds both revisions before measuring either one on the same runner.
+ # Building first avoids CPU, IO, thermal, cache, and frequency state biasing a measurement.
+ # Shard a measures BASE then HEAD, while shard b measures HEAD then BASE.
+ # Alternating the order cancels first-versus-second ordering bias without doubling runtime.
+ # Both shards use the PR merge commit as HEAD, so they measure what would actually land.
+ # Results are advisory: a detected regression never fails this workflow.
+ benchmark:
+ name: "Paired JMH benchmarks (${{ matrix.shard }})"
+ if: >-
+ contains(github.event.pull_request.labels.*.name, 'performance') &&
+ (github.event.action != 'labeled' || github.event.label.name == 'performance')
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ shard: [a, b]
+ env:
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ HEAD_SHA: ${{ github.sha }}
+ JMH_INCLUDE: '.*'
+ RESULT_DIR: ${{ github.workspace }}/jmh-results/${{ matrix.shard }}
+ REPORT_DIR: ${{ github.workspace }}/jmh-reports/${{ matrix.shard }}
+ SHARD: ${{ matrix.shard }}
+ steps:
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ with:
+ fetch-depth: 0
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ # Deliberately allowed to fail the job, and placed before the expensive work: a broken
+ # comparison tool makes every number produced here untrustworthy. That is a tooling
+ # failure rather than a performance finding, so regressions themselves stay advisory.
+ - name: "๐งช Verify JMH comparison tool"
+ run: ./gradlew :grails-test-examples-benchmarks:test --max-workers=4
+ - name: "๐ณ Prepare paired worktrees"
+ run: |
+ WORKTREE_ROOT="$RUNNER_TEMP/jmh-worktrees/$SHARD"
+ mkdir -p "$WORKTREE_ROOT" "$RESULT_DIR" "$REPORT_DIR"
+ if [ -n "$BASE_SHA" ] && git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then
+ RESOLVED_BASE_SHA="$BASE_SHA"
+ elif RESOLVED_BASE_SHA="$(git merge-base "$HEAD_SHA^1" "$HEAD_SHA^2" 2>/dev/null)"; then
+ echo "Configured base commit is unreachable; using merge-base $RESOLVED_BASE_SHA."
+ elif RESOLVED_BASE_SHA="$(git rev-parse "$HEAD_SHA^" 2>/dev/null)"; then
+ echo "Configured base commit is unreachable; using HEAD parent $RESOLVED_BASE_SHA."
+ else
+ RESOLVED_BASE_SHA=""
+ echo "No base commit could be resolved; comparison will use HEAD-only mode."
+ fi
+ git worktree add --detach "$WORKTREE_ROOT/head" "$HEAD_SHA"
+ {
+ printf 'HEAD_DIR=%s\n' "$WORKTREE_ROOT/head"
+ printf 'WORKTREE_ROOT=%s\n' "$WORKTREE_ROOT"
+ } >> "$GITHUB_ENV"
+ if [ -n "$RESOLVED_BASE_SHA" ] && git worktree add --detach "$WORKTREE_ROOT/base" "$RESOLVED_BASE_SHA"; then
+ {
+ printf 'BASE_DIR=%s\n' "$WORKTREE_ROOT/base"
+ printf 'RESOLVED_BASE_SHA=%s\n' "$RESOLVED_BASE_SHA"
+ } >> "$GITHUB_ENV"
+ echo "Using base commit $RESOLVED_BASE_SHA."
+ else
+ echo "BASE_BENCHMARKS_AVAILABLE=false" >> "$GITHUB_ENV"
+ fi
+ # Build both JMH jars before measuring either revision. Builds are CPU- and IO-heavy,
+ # so building and measuring one revision at a time would bias results with different
+ # thermal, cache, and CPU-frequency state on the shared runner.
+ - name: "๐จ Build paired JMH jars"
+ timeout-minutes: 60
+ run: |
+ base_build_ok=false
+ head_build_ok=false
+ if [ "${BASE_BENCHMARKS_AVAILABLE:-true}" = "true" ] && [ -f "$BASE_DIR/grails-test-examples/benchmarks/build.gradle" ]; then
+ if (
+ cd "$BASE_DIR"
+ ./gradlew :grails-test-examples-benchmarks:jmhJar --max-workers=4
+ ); then
+ base_build_ok=true
+ echo "Built base benchmark at $RESOLVED_BASE_SHA."
+ else
+ echo "BASE benchmark JAR build failed."
+ fi
+ else
+ echo "BASE does not contain the benchmarks project; comparison will use HEAD-only mode."
+ fi
+ if (
+ cd "$HEAD_DIR"
+ ./gradlew :grails-test-examples-benchmarks:jmhJar --max-workers=4
+ ); then
+ head_build_ok=true
+ echo "Built HEAD benchmark at $HEAD_SHA."
+ else
+ echo "HEAD benchmark JAR build failed."
+ fi
+ {
+ printf 'BASE_BUILD_OK=%s\n' "$base_build_ok"
+ printf 'HEAD_BUILD_OK=%s\n' "$head_build_ok"
+ } >> "$GITHUB_ENV"
+ - name: "๐ Stop Gradle daemons before measurement"
+ if: always()
+ run: |
+ ./gradlew --stop || true
+ if [ -n "${BASE_DIR:-}" ] && [ -d "$BASE_DIR" ]; then (cd "$BASE_DIR" && ./gradlew --stop) || true; fi
+ if [ -n "${HEAD_DIR:-}" ] && [ -d "$HEAD_DIR" ]; then (cd "$HEAD_DIR" && ./gradlew --stop) || true; fi
+ # Two forks, three warmup iterations, and five measurement iterations balance PR latency
+ # against confidence. Reversing shard order cancels first-versus-second runner-state bias.
+ - name: "๐ก๏ธ Run paired JMH benchmarks"
+ timeout-minutes: 60
+ run: |
+ # JMH writes results incrementally, so a run that dies partway can leave a file that
+ # parses perfectly while describing only some of the benchmarks. The report job decides
+ # completeness from which files exist, so a partial file would be indistinguishable from
+ # a good one. Write to a staging path and publish it only on success, so a failed run
+ # leaves NO file rather than a plausible one.
+ run_benchmark() {
+ local revision_dir="$1"
+ local result_file="$2"
+ local staging_file="$result_file.partial"
+ rm -f "$staging_file" "$result_file"
+ if (
+ cd "$revision_dir" && ./gradlew --no-daemon :grails-test-examples-benchmarks:jmh \
+ -Pjmh.include="$JMH_INCLUDE" \
+ -Pjmh.forks=2 \
+ -Pjmh.warmupIterations=3 \
+ -Pjmh.iterations=5 \
+ -Pjmh.resultFile="$staging_file" \
+ -Pjmh.profilers=gc \
+ --max-workers=4
+ ) && [ -s "$staging_file" ] && mv "$staging_file" "$result_file"; then
+ return 0
+ fi
+ rm -f "$staging_file"
+ return 1
+ }
+
+ base_run_failed=false
+ head_run_failed=false
+ run_base_benchmark() {
+ if [ "${BASE_BUILD_OK:-false}" != "true" ]; then
+ echo "BASE benchmark execution skipped because its JAR was not built."
+ elif ! run_benchmark "$BASE_DIR" "$RESULT_DIR/base.json"; then
+ base_run_failed=true
+ echo "BASE benchmark execution failed."
+ fi
+ }
+ run_head_benchmark() {
+ if [ "${HEAD_BUILD_OK:-false}" != "true" ]; then
+ echo "HEAD benchmark execution skipped because its JAR was not built."
+ elif ! run_benchmark "$HEAD_DIR" "$RESULT_DIR/head.json"; then
+ head_run_failed=true
+ echo "HEAD benchmark execution failed."
+ fi
+ }
+
+ if [ "$SHARD" = "a" ]; then
+ run_base_benchmark
+ run_head_benchmark
+ else
+ run_head_benchmark
+ run_base_benchmark
+ fi
+ {
+ printf 'BASE_RUN_FAILED=%s\n' "$base_run_failed"
+ printf 'HEAD_RUN_FAILED=%s\n' "$head_run_failed"
+ } >> "$GITHUB_ENV"
+ # The comparison is advisory. The comparison tool exits successfully for regressions, and this
+ # step is non-blocking even if results are incomplete because a benchmark execution failed.
+ # No --pr-number is passed here on purpose: this job renders the report only. The separate
+ # report job owns comment posting, so a two-shard matrix cannot produce duplicate comments.
+ - name: "๐ Compare JMH results"
+ if: always()
+ continue-on-error: true
+ run: |
+ report_file="$REPORT_DIR/comparison.md"
+ {
+ printf '### JMH shard `%s`\n\n' "$SHARD"
+ if [ "${BASE_RUN_FAILED:-false}" = "true" ]; then
+ printf 'BASE benchmark execution failed.\n\n'
+ fi
+ if [ "${BASE_BUILD_OK:-false}" != "true" ]; then
+ printf 'BASE benchmark JAR was not built.\n\n'
+ fi
+ if [ "${HEAD_RUN_FAILED:-false}" = "true" ]; then
+ printf 'HEAD benchmark execution failed.\n\n'
+ fi
+ if [ "${HEAD_BUILD_OK:-false}" != "true" ]; then
+ printf 'HEAD benchmark JAR was not built.\n\n'
+ fi
+ } > "$report_file"
+ comparison_file="$RUNNER_TEMP/jmh-comparison-$SHARD.md"
+ if [ ! -f "$RESULT_DIR/head.json" ]; then
+ printf 'HEAD benchmark result was not produced.\n' >> "$report_file"
+ elif [ "${BASE_BUILD_OK:-false}" = "true" ] && [ -f "$RESULT_DIR/base.json" ]; then
+ ./gradlew -q --console=plain :grails-test-examples-benchmarks:jmhCompare --args="--head $RESULT_DIR/head.json --base $RESULT_DIR/base.json --output $comparison_file"
+ cat "$comparison_file" >> "$report_file"
+ else
+ ./gradlew -q --console=plain :grails-test-examples-benchmarks:jmhCompare --args="--head $RESULT_DIR/head.json --output $comparison_file"
+ cat "$comparison_file" >> "$report_file"
+ fi
+ - name: "๐ Publish JMH report in job summary"
+ if: always()
+ run: |
+ if [ -f "$REPORT_DIR/comparison.md" ]; then
+ cat "$REPORT_DIR/comparison.md" >> "$GITHUB_STEP_SUMMARY"
+ else
+ printf '## JMH benchmark comparison\n\nNo comparison report was produced.\n' >> "$GITHUB_STEP_SUMMARY"
+ fi
+ - name: "๐ค Upload JMH artifacts"
+ if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
++ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: jmh-results-${{ matrix.shard }}
+ path: |
+ jmh-results/${{ matrix.shard }}/
+ jmh-reports/${{ matrix.shard }}/
+ if-no-files-found: warn
+ - name: "๐งน Remove paired worktrees"
+ if: always()
+ run: |
+ WORKTREE_ROOT="$RUNNER_TEMP/jmh-worktrees/$SHARD"
+ git worktree remove --force "$WORKTREE_ROOT/base" || true
+ git worktree remove --force "$WORKTREE_ROOT/head" || true
+
+ report:
+ name: "Publish JMH benchmark comparison"
+ needs: benchmark
+ if: >-
+ always() && github.event_name == 'pull_request' &&
+ contains(github.event.pull_request.labels.*.name, 'performance') &&
+ (github.event.action != 'labeled' || github.event.label.name == 'performance') &&
+ github.event.pull_request.head.repo.full_name == github.repository
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+ pull-requests: write
+ env:
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ REPOSITORY: ${{ github.repository }}
+ EXPECTED_SHARDS: "a b"
+ steps:
+ # This job runs only for same-repository pull requests, whose authors already hold push
+ # access, so checking out the merge commit to run the Gradle-based comparison tool is not a privilege
+ # escalation. Fork pull requests never reach this job: their pull_request token is
+ # read-only and commenting would fail with 403, so they receive the per-shard job summary
+ # and the uploaded artifacts instead.
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ฅ Download JMH artifacts"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
++ uses: actions/download-artifact@v8.0.1
+ with:
+ path: benchmark-artifacts
+ # Both shards measured the same two revisions in opposite orders. Only complete same-shard
+ # base/head pairs are pooled, preserving runner identity while letting the alternating order
+ # cancel ordering bias. Incomplete shards are reported and never cross-paired.
+ - name: "๐ Pool shard results and compare"
+ id: compare
+ run: |
+ collected_base="$RUNNER_TEMP/collected-base"
+ collected_head="$RUNNER_TEMP/collected-head"
+ pooled_base="$RUNNER_TEMP/pooled-base"
+ pooled_head="$RUNNER_TEMP/pooled-head"
+ head_only="$RUNNER_TEMP/head-only"
+ mkdir -p "$collected_base" "$collected_head" "$pooled_base" "$pooled_head" "$head_only"
+ # Seeded from the matrix rather than from discovered files: a shard that failed before
+ # uploading anything has no file to discover, and would otherwise be omitted from the
+ # report entirely instead of being named as missing.
+ declare -A observed_shards=()
+ expected_shard_files=""
+ for shard in $EXPECTED_SHARDS; do
+ observed_shards["$shard"]=true
+ expected_shard_files="${expected_shard_files:+$expected_shard_files,}shard-$shard.json"
+ done
+ while IFS= read -r -d '' result_file; do
+ shard=""
+ if [[ "$result_file" =~ /jmh-results-([^/]+)/ ]]; then
+ shard="${BASH_REMATCH[1]}"
+ elif [[ "$result_file" =~ /jmh-results/([^/]+)/ ]]; then
+ shard="${BASH_REMATCH[1]}"
+ else
+ printf 'Unable to identify shard for result %s; ignoring it.\n' "$result_file"
+ continue
+ fi
+ observed_shards["$shard"]=true
+ case "$(basename "$result_file")" in
+ base.json) cp "$result_file" "$collected_base/shard-$shard.json" ;;
+ head.json) cp "$result_file" "$collected_head/shard-$shard.json" ;;
+ esac
+ done < <(find benchmark-artifacts -type f \( -name 'base.json' -o -name 'head.json' \) -print0)
+
+ complete_shards=()
+ dropped_shards=()
+ if [ "${#observed_shards[@]}" -gt 0 ]; then
+ for shard in "${!observed_shards[@]}"; do
+ if [ -f "$collected_base/shard-$shard.json" ] && [ -f "$collected_head/shard-$shard.json" ]; then
+ cp "$collected_base/shard-$shard.json" "$pooled_base/shard-$shard.json"
+ cp "$collected_head/shard-$shard.json" "$pooled_head/shard-$shard.json"
+ complete_shards+=("$shard")
+ else
+ dropped_shards+=("$shard")
+ fi
+ if [ -f "$collected_head/shard-$shard.json" ]; then
+ cp "$collected_head/shard-$shard.json" "$head_only/shard-$shard.json"
+ fi
+ done
+ fi
+
+ report_file="$RUNNER_TEMP/pooled-comparison.md"
+ comparison_file="$RUNNER_TEMP/jmh-comparison.md"
+ {
+ printf '### JMH Shard Pairing\n\n'
+ printf '**Complete shard pairs used:** %s\n' "${#complete_shards[@]}"
+ if [ "${#dropped_shards[@]}" -gt 0 ]; then
+ printf '**Dropped incomplete shards:**'
+ printf ' `%s`' "${dropped_shards[@]}"
+ printf '\n'
+ fi
+ printf '\n'
+ } > "$report_file"
+ should_post=false
+ if [ "${#complete_shards[@]}" -gt 0 ]; then
+ should_post=true
+ ./gradlew -q --console=plain :grails-test-examples-benchmarks:jmhCompare --args="--head $pooled_head --base $pooled_base --expected-shards $expected_shard_files --output $comparison_file"
+ cat "$comparison_file" >> "$report_file"
+ elif compgen -G "$head_only/*.json" > /dev/null; then
+ should_post=true
+ printf 'No complete same-runner base and HEAD shard pair was available. Reporting available HEAD results only.\n\n' >> "$report_file"
+ ./gradlew -q --console=plain :grails-test-examples-benchmarks:jmhCompare --args="--head $head_only --output $comparison_file"
+ cat "$comparison_file" >> "$report_file"
+ else
+ printf '### JMH Benchmark Report\n\nNo HEAD benchmark results were produced for this run. The previous JMH report comment on this PR (if any) was left unchanged.\n' >> "$report_file"
+ fi
+ cat "$report_file" >> "$GITHUB_STEP_SUMMARY"
+ printf 'report_file=%s\n' "$report_file" >> "$GITHUB_OUTPUT"
+ printf 'should_post=%s\n' "$should_post" >> "$GITHUB_OUTPUT"
+ - name: "๐ฌ Post or update JMH PR comment"
+ if: steps.compare.outputs.should_post == 'true'
+ continue-on-error: true
+ env:
+ REPORT_FILE: ${{ steps.compare.outputs.report_file }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: ./gradlew -q --console=plain :grails-test-examples-benchmarks:jmhCompare --args="--post-file $REPORT_FILE --repo $REPOSITORY --pr-number $PR_NUMBER"
diff --cc .github/workflows/codeanalysis.yml
index f5e0180047,0000000000..ba99bae0cf
mode 100644,000000..100644
--- a/.github/workflows/codeanalysis.yml
+++ b/.github/workflows/codeanalysis.yml
@@@ -1,97 -1,0 +1,97 @@@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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. You may obtain a copy of the License at
+#
+# https://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.
+
+name: "Code Analysis"
+on:
+ push:
+ branches:
+ - '[0-9]+.[0-9]+.x'
+ - '8.0.x-hibernate7.*'
+ pull_request:
+ workflow_dispatch:
+# queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+ check_core_projects:
+ name: "Core Projects"
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "๐ Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Check Core Projects"
+ run: ./gradlew aggregateAnalysisViolations --continue -Pgrails.code-analysis.enabled.pmd=true -Pgrails.code-analysis.enabled.spotbugs=true -Pgrails.code-analysis.ignoreFailures=true
+ - name: "๐ค Upload Reports"
+ if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
++ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: core-reports
+ path: build/reports/violations/
+ - name: "๐ Publish Code Analysis Report in Job Summary"
+ if: always()
+ run: |
+ echo "## ๐ Code Analysis Report - Core Projects" >> $GITHUB_STEP_SUMMARY
+ for report in PMD_VIOLATIONS.md SPOTBUGS_VIOLATIONS.md; do
+ file="build/reports/violations/$report"
+ [ -f "$file" ] && cat "$file" >> $GITHUB_STEP_SUMMARY || true
+ done
+ check_gradle_plugin_projects:
+ name: "Gradle Plugin Projects"
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "๐ Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Check Gradle Plugin Projects"
+ working-directory: grails-gradle
+ run: ./gradlew aggregateAnalysisViolations --continue -Pgrails.code-analysis.enabled.pmd=true -Pgrails.code-analysis.enabled.spotbugs=true -Pgrails.code-analysis.ignoreFailures=true
+ - name: "๐ค Upload Reports"
+ if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
++ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: gradle-plugin-reports
+ path: grails-gradle/build/reports/violations/
+ - name: "๐ Publish Code Analysis Report in Job Summary"
+ if: always()
+ run: |
+ echo "## ๐ Code Analysis Report - Gradle Plugin Projects" >> $GITHUB_STEP_SUMMARY
+ for report in PMD_VIOLATIONS.md SPOTBUGS_VIOLATIONS.md; do
+ file="grails-gradle/build/reports/violations/$report"
+ [ -f "$file" ] && cat "$file" >> $GITHUB_STEP_SUMMARY || true
+ done
diff --cc .github/workflows/codeql.yml
index 8fc297a210,bc1430f8c7..1f8572fec4
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@@ -58,19 -56,17 +58,19 @@@ jobs
steps:
- name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
+ uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
languages: ${{ matrix.language }}
+ # The autobuild action runs `./gradlew testClasses`, which compiles Groovy sample apps that are covered by regular CI.
+ build-mode: none
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
diff --cc .github/workflows/codestyle.yml
index a82a9d12a9,772ad775f5..62d100c515
--- a/.github/workflows/codestyle.yml
+++ b/.github/workflows/codestyle.yml
@@@ -33,25 -32,25 +33,25 @@@ jobs
- name: "๐ Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
run: curl -s https://api.ipify.org
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐ Setup Gradle"
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
- name: "๐ Check Core Projects"
- run: ./gradlew codeStyle
- - name: "๐ค Upload Failure Reports"
+ run: ./gradlew aggregateStyleViolations --continue
+ - name: "๐ค Upload Reports"
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@v7.0.1
with:
name: core-reports
- path: build/reports/codestyle/
+ path: build/reports/violations/
- name: "๐ Publish Code Style Report in Job Summary"
if: always()
run: |
@@@ -67,12 -70,12 +67,12 @@@
- name: "๐ Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
run: curl -s https://api.ipify.org
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐ Setup Gradle"
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
@@@ -83,10 -86,10 +83,10 @@@
run: ./gradlew codeStyle
- name: "๐ค Upload Failure Reports"
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@v7.0.1
with:
- name: gradle-plugin-reports
- path: grails-gradle/build/reports/codestyle/
+ name: forge-reports
+ path: grails-forge/build/reports/code-style/
- name: "๐ Publish Code Style Report in Job Summary"
if: always()
run: |
@@@ -106,26 -109,26 +106,26 @@@
- name: "๐ Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
run: curl -s https://api.ipify.org
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐ Setup Gradle"
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
- - name: "๐ Check Forge Projects"
- working-directory: grails-forge
- run: ./gradlew codeStyle
- - name: "๐ค Upload Failure Reports"
+ - name: "๐ Check Gradle Plugin Projects"
+ working-directory: grails-gradle
+ run: ./gradlew aggregateStyleViolations --continue
+ - name: "๐ค Upload Reports"
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@v7.0.1
with:
- name: forge-reports
- path: grails-forge/build/reports/codestyle/
+ name: gradle-plugin-reports
+ path: grails-gradle/build/reports/violations/
- name: "๐ Publish Code Style Report in Job Summary"
if: always()
run: |
diff --cc .github/workflows/coverage.yml
index 0afea8f632,0000000000..99e9ab4b8f
mode 100644,000000..100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@@ -1,119 -1,0 +1,119 @@@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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. You may obtain a copy of the License at
+#
+# https://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.
+
+name: "Coverage"
+
+on:
+ push:
+ branches:
+ - '[0-9]+.[0-9]+.x'
+ - '8.0.x-hibernate7.*'
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+ coverage-core:
+ name: "Coverage - grails-core (${{ matrix.os }})"
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-24.04]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ก๏ธ Run tests with coverage"
+ run: >
+ ./gradlew jacocoAggregateReport
+ --continue
+ --stacktrace
+ -PskipCodeStyle
+ - name: "๐ค Upload coverage artifact"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
++ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: coverage-core-${{ matrix.os }}
+ path: build/reports/jacoco/aggregate/jacocoAggregateReport.xml
+ if-no-files-found: warn
+
+ coverage-gradle:
+ name: "Coverage - grails-gradle (${{ matrix.os }})"
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-24.04]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ก๏ธ Run tests with coverage"
+ working-directory: grails-gradle
+ run: >
+ ./gradlew jacocoAggregateReport
+ --continue
+ --stacktrace
+ -PskipCodeStyle
+ - name: "๐ค Upload coverage artifact"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
++ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: coverage-gradle-${{ matrix.os }}
+ path: grails-gradle/build/reports/jacoco/aggregate/jacocoAggregateReport.xml
+ if-no-files-found: warn
+
+ upload-coverage:
+ name: "Upload Coverage to Codecov"
+ needs: [coverage-core, coverage-gradle]
+ # Run even if some matrix legs fail so partial coverage is still uploaded
+ if: always()
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "๐ฅ Download all coverage artifacts"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
++ uses: actions/download-artifact@v8.0.1
+ with:
+ path: coverage-reports
+ - name: "๐ Upload coverage to Codecov"
+ continue-on-error: true
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ directory: coverage-reports
+ verbose: true
diff --cc .github/workflows/end-to-end.yml
index 22b5e04531,0000000000..7a987d327b
mode 100644,000000..100644
--- a/.github/workflows/end-to-end.yml
+++ b/.github/workflows/end-to-end.yml
@@@ -1,149 -1,0 +1,149 @@@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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. You may obtain a copy of the License at
+#
+# https://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.
+
+# The end-to-end suite is its own Gradle build, so the core build never reaches it and
+# `./gradlew build` at the root is unaffected.
+#
+# It resolves Grails from the artifacts the core build publishes rather than by project
+# substitution - that is what makes the tests end-to-end. The repository is the same
+# build/local-maven that grails-forge points its generated applications at, populated by
+# publishAllPublicationsToTestCaseMavenRepoRepository, so the suite exercises real poms and
+# module metadata including the CLI companion artifacts. That is a hard requirement for some of
+# these projects rather than a nicety: spring-dependency-management imports grails-bom as a Maven
+# BOM through io.spring.dependency-management, which resolves it in its own detached configuration
+# and so can only ever consume a published pom.
+#
+# It also needs two JDKs, which is the other reason it gets its own workflow: the Grails 7
+# fixture must be compiled on Java 17 (the minimum for a Grails 7 app, so the binary matches
+# what a real Grails 7 plugin is built with), while the core build and the Grails 8
+# application consuming the fixture need 21. The Grails 8 side simply tracks the repository's
+# root .sdkmanrc - it has to run on whatever the core build it consumes runs on - and only the
+# fixture carries its own pin. The steps below read both out of those files rather than relying
+# on Gradle toolchain auto-detection.
+name: "End to End"
+on:
+ push:
+ branches:
+ - '[0-9]+.[0-9]+.x'
+ pull_request:
+ workflow_dispatch:
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+ endToEnd:
+ name: "End to End Tests (end-to-end build only)"
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Determine JDKs from .sdkmanrc"
+ # Read both pins out of the files that already declare them. Only the major is kept:
+ # `sdk env` gives a developer the exact Liberica patch, but the fixture and the suite are
+ # test builds outside the reproducible-build surface, so CI deliberately takes the
+ # runner's current release of each major rather than pinning patch versions here.
+ id: jdks
+ run: |
+ set -euo pipefail
+ fixture_java=$(grep -E '^java=' end-to-end/legacy-g7-command-plugin/.sdkmanrc | cut -d= -f2)
+ build_java=$(grep -E '^java=' .sdkmanrc | cut -d= -f2)
+ echo "fixture-java=${fixture_java%%.*}" >> "$GITHUB_OUTPUT"
+ echo "build-java=${build_java%%.*}" >> "$GITHUB_OUTPUT"
+ echo "Grails 7 fixture JDK: ${fixture_java}"
+ echo "end-to-end build JDK (from root .sdkmanrc): ${build_java}"
+ - name: "โ๏ธ Setup JDKs"
+ # Both, in one step. The last version listed becomes the default JAVA_HOME (the Grails 8
+ # side); the fixture step below switches to the matching JAVA_HOME_<major>_X64 for its
+ # single invocation.
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: |
+ ${{ steps.jdks.outputs.fixture-java }}
+ ${{ steps.jdks.outputs.build-java }}
+ - name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache@v6.1.0
+ with:
+ # Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
+ # Keyed by branch version so each release branch maintains its own warm cache.
+ path: |
+ ~/.gradle/caches/modules-2
+ ~/.gradle/wrapper
+ key: gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-${{ hashFiles('**/dependencies.gradle', '**/gradle-wrapper.properties') }}
+ restore-keys: |
+ gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-disabled: true # dependency jars are cached by the explicit branch-keyed step above
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ฆ Setup: publish grails-gradle to the local repository the tests resolve from"
+ # Both builds publish into the same build/local-maven, and both are needed: the Grails BOM
+ # constrains org.apache.grails.gradle artifacts. grails-forge depends on this same pair of
+ # publish tasks for the applications its tests generate.
+ working-directory: 'grails-gradle'
+ run: ./gradlew publishAllPublicationsToTestCaseMavenRepoRepository -PskipTests --stacktrace
+ - name: "๐ฆ Setup: publish Grails to the local repository the tests resolve from"
+ run: ./gradlew publishAllPublicationsToTestCaseMavenRepoRepository -PskipTests --stacktrace
+ - name: "๐ฆ Setup: build the precompiled Grails 7 / Groovy 4 fixture"
+ working-directory: 'end-to-end/legacy-g7-command-plugin'
+ env:
+ # Derived from the major the jdks step read out of the fixture's .sdkmanrc, so a fixture
+ # JDK bump changes this lookup with it instead of leaving a stale literal behind.
+ JAVA_HOME: ${{ env[format('JAVA_HOME_{0}_X64', steps.jdks.outputs.fixture-java)] }}
+ run: |
+ set -euo pipefail
+ if [ -z "${JAVA_HOME:-}" ]; then
+ echo "setup-java did not provision JDK ${{ steps.jdks.outputs.fixture-java }}; refusing to build the fixture on the default JDK" >&2
+ exit 1
+ fi
+ ./gradlew jar --stacktrace
+ - name: "๐ Verify the fixture really was built by the Grails 7 toolchain"
+ working-directory: 'end-to-end/legacy-g7-command-plugin'
+ # A fixture silently built by the wrong toolchain would still pass the suite while
+ # proving nothing, so fail loudly here instead. Grails-/Groovy-Compile-Version come from
+ # the resolved BOM and Build-Jdk-Spec from the JVM that ran Gradle (all stamped into the
+ # manifest at jar time); the class-file major version is the ground truth for what the
+ # bytecode actually targets, since the fixture deliberately sets no toolchain or release.
+ run: |
+ set -euo pipefail
+ fixture_java='${{ steps.jdks.outputs.fixture-java }}'
+ jar=$(ls build/libs/*.jar)
+ unzip -p "$jar" META-INF/MANIFEST.MF | tr -d '\r' > /tmp/fixture-manifest
+ cat /tmp/fixture-manifest
+ grep -q '^Grails-Compile-Version: 7\.' /tmp/fixture-manifest
+ grep -q '^Groovy-Compile-Version: 4\.' /tmp/fixture-manifest
+ grep -q "^Build-Jdk-Spec: ${fixture_java}\$" /tmp/fixture-manifest
+ expected_major=$((fixture_java + 44))
+ actual_major=$(unzip -p "$jar" legacy/g7/commands/HelloG7PrecompiledCommand.class \
+ | od -An -t u1 -j 6 -N 2 | awk 'NF { print $1 * 256 + $2; exit }')
+ echo "class-file major version: ${actual_major} (expected ${expected_major} for Java ${fixture_java})"
+ [ "$actual_major" -eq "$expected_major" ]
+ - name: "๐ Setup TestLens"
+ uses: testlens-app/setup-testlens@3f82d2dc6cd5c03f02ce5f7885a21195d85f8d14 # v1.9.3
+ - name: "๐งช Run the end-to-end tests"
+ # Only the end-to-end build. grails-core's own unit and functional suites are the CI
+ # workflow's job; nothing here re-runs them. The publish steps above are setup, not tests.
+ working-directory: 'end-to-end'
+ env:
+ DO_NOT_CACHE_TESTS: '1'
+ run: ./gradlew check --continue --stacktrace
+ - name: "๐ค Upload test reports"
+ if: failure()
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
++ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: end-to-end-test-reports
+ path: end-to-end/**/build/reports/tests/**
diff --cc .github/workflows/gradle.yml
index 7c3e935c89,d95e4abce3..d1fbadc5cb
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@@ -30,14 -30,14 +30,14 @@@ jobs
runs-on: ubuntu-24.04
steps:
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
+ uses: actions/cache@v6.1.0
with:
# Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
# Keyed by branch version so each release branch maintains its own warm cache.
@@@ -182,8 -145,7 +182,8 @@@
distribution: liberica
java-version: ${{ matrix.java }}
- name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@v6.1.0
+ id: dependency-cache
- uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache/restore@v6.1.0
with:
# Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
# Keyed by branch version so each release branch maintains its own warm cache.
@@@ -207,19 -169,9 +207,19 @@@
--stacktrace
-PonlyCoreTests
-PskipCodeStyle
+ ${{ matrix.shard_arguments }}
+ ${{ matrix.runner_arguments }}
+ - name: "๐๏ธ Save dependency jar cache"
+ if: ${{ success() && matrix.cache_writer && steps.dependency-cache.outputs.cache-hit != 'true' }}
- uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache/save@v6.1.0
+ with:
+ path: |
+ ~/.gradle/caches/modules-2
+ ~/.gradle/wrapper
+ key: gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-${{ hashFiles('**/dependencies.gradle', '**/gradle-wrapper.properties') }}
buildRerunTasks:
if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
- name: 'Build Grails-Core Rerunning all Tasks'
+ name: 'Build Grails-Core Rerunning all Tasks (${{ matrix.job_name }})'
strategy:
fail-fast: false
matrix:
@@@ -260,8 -190,7 +260,8 @@@
distribution: liberica
java-version: ${{ matrix.java }}
- name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@v6.1.0
+ id: dependency-cache
- uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache/restore@v6.1.0
with:
# Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
# Keyed by branch version so each release branch maintains its own warm cache.
@@@ -286,15 -215,6 +286,15 @@@
--stacktrace
-PonlyCoreTests
-PskipCodeStyle
+ ${{ matrix.extra_arguments }}
+ - name: "๐๏ธ Save dependency jar cache"
+ if: ${{ success() && matrix.cache_writer && steps.dependency-cache.outputs.cache-hit != 'true' }}
- uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache/save@v6.1.0
+ with:
+ path: |
+ ~/.gradle/caches/modules-2
+ ~/.gradle/wrapper
+ key: gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-${{ hashFiles('**/dependencies.gradle', '**/gradle-wrapper.properties') }}
buildForge:
name: "Build Grails Forge (Java ${{ matrix.java }}, indy=${{ matrix.indy }})"
strategy:
@@@ -363,8 -283,8 +363,8 @@@
./tmp1/cli/bin/grails --version
./tmp1/cli/bin/grails-forge-cli --version
- name: "๐ค Upload CLI Zip to Workflow Summary Page"
- if: ${{ matrix.java == '17' && matrix.indy == false }}
+ if: ${{ matrix.java == '21' && matrix.indy == false }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@v7.0.1
with:
name: 'apache-grails-SNAPSHOT-bin.zip'
include-hidden-files: true
@@@ -435,8 -313,7 +435,8 @@@
distribution: liberica
java-version: ${{ matrix.java }}
- name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@v6.1.0
+ id: dependency-cache
- uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache/restore@v6.1.0
with:
# Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
# Keyed by branch version so each release branch maintains its own warm cache.
@@@ -463,25 -340,14 +463,25 @@@
-PonlyFunctionalTests
-PskipCodeStyle
-PskipHibernate5Tests
+ -PskipHibernate7Tests
-PskipMongodbTests
+ -PskipNeo4jTests
-PskipSpringSecurityTests
-PskipRedisTests
+ ${{ matrix.shard_arguments }}
+ - name: "๐๏ธ Save dependency jar cache"
+ if: ${{ success() && matrix.cache_writer && steps.dependency-cache.outputs.cache-hit != 'true' }}
- uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache/save@v6.1.0
+ with:
+ path: |
+ ~/.gradle/caches/modules-2
+ ~/.gradle/wrapper
+ key: gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-${{ hashFiles('**/dependencies.gradle', '**/gradle-wrapper.properties') }}
- name: "๐ค Upload Geb Reports"
if: failure()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@v7.0.1
with:
- name: geb-reports-functional-java${{ matrix.java }}-indy${{ matrix.indy }}
+ name: geb-reports-functional-${{ matrix.artifact_suffix }}
path: '**/build/geb-reports/'
if-no-files-found: ignore
security:
@@@ -548,12 -400,12 +548,12 @@@
- name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
run: curl -s https://api.ipify.org
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐ Setup Gradle"
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
@@@ -570,48 -422,6 +570,48 @@@
--stacktrace
-DTESTCONFIG=${{ matrix.test-config }}
-PskipCodeStyle
+ casSecurityConfig:
+ name: "Spring Security CAS Functional Tests"
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ timeout-minutes: 90 # slowest run: 35 min
+ strategy:
+ fail-fast: false
+ matrix:
+ # The CAS test app authenticates against a real Apereo CAS server started via Testcontainers.
+ # 'cas' leaves the proxy settings unset; 'casProxy' exercises the proxy-granting-ticket
+ # callback; 'casNoSingleSignout' asserts the shipped default, where single signout is off.
+ # TESTCONFIG is applied at app startup, so every config must be its own run. The specs are
+ # gated on a config being selected, so the broad 'security' job skips them and no config is
+ # run twice.
+ # Carried over from the standalone grails-spring-security CI convention - add, don't remove.
+ test-config: [ 'cas', 'casProxy', 'casNoSingleSignout' ]
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Setup TestLens"
+ uses: testlens-app/setup-testlens@3f82d2dc6cd5c03f02ce5f7885a21195d85f8d14 # v1.9.3
+ - name: "๐ Run Spring Security CAS Functional Tests (TESTCONFIG=${{ matrix.test-config }})"
+ run: >
+ ./gradlew
+ :grails-test-examples-spring-security-cas-test1:check
+ --continue
+ --rerun-tasks
+ --stacktrace
+ -DTESTCONFIG=${{ matrix.test-config }}
+ -PskipCodeStyle
redis:
name: "Redis Tests"
if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
@@@ -684,17 -492,6 +684,17 @@@
key: gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-${{ hashFiles('**/dependencies.gradle', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-
+ - name: "๐๏ธ Cache the mongod the embedded server runs"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache@v6.1.0
+ with:
+ # Kept apart from the dependency jars: this holds an immutable download from
+ # fastdl.mongodb.org - the archive and the binary extracted out of it - and no database,
+ # so it belongs to no branch and does not change when a dependency version does.
+ # Keyed by the version this entry asks for, because the matrix runs more than one and a
+ # single key would keep whichever job missed first and leave the other restoring 250MB
+ # of a binary it cannot use. No restore-keys, for that same reason.
+ path: ~/.embedmongo
+ key: embedmongo-${{ runner.os }}-${{ matrix.mongodb-version }}
- name: "๐ Setup Gradle"
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
@@@ -762,116 -558,6 +762,116 @@@
-PgrailsIndy=${{ matrix.indy }}
-PonlyHibernate5Tests
-PskipCodeStyle
+ hibernate7Functional:
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ name: "Hibernate7 Functional Tests (${{ matrix.job_name }})"
+ timeout-minutes: 210 # slowest run: 104 min, which failed; the slowest that passed took 86
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - java: 21
+ indy: false
+ job_name: Java 21, indy=false
+ gradle_task: 'bootJar check'
+ shard_arguments: '-PtestShardCount=3 -PtestShardIndex=0'
+ - java: 21
+ indy: false
+ job_name: Java 21, indy=false, shard 1
+ gradle_task: testShard
+ shard_arguments: '-PtestShardCount=3 -PtestShardIndex=1'
+ - java: 21
+ indy: false
+ job_name: Java 21, indy=false, shard 2
+ gradle_task: testShard
+ shard_arguments: '-PtestShardCount=3 -PtestShardIndex=2'
+ - java: 21
+ indy: true
+ job_name: Java 21, indy=true
+ gradle_task: 'bootJar check'
+ shard_arguments: '-PtestShardCount=2 -PtestShardIndex=0'
+ - java: 21
+ indy: true
+ job_name: Java 21, indy=true, shard 1
+ gradle_task: testShard
+ shard_arguments: '-PtestShardCount=2 -PtestShardIndex=1'
+ - java: 25
+ indy: false
+ job_name: Java 25, indy=false
+ gradle_task: 'bootJar check'
+ shard_arguments: '-PtestShardCount=2 -PtestShardIndex=0'
+ - java: 25
+ indy: false
+ job_name: Java 25, indy=false, shard 1
+ gradle_task: testShard
+ shard_arguments: '-PtestShardCount=2 -PtestShardIndex=1'
+ steps:
+ - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "๐ฅ Checkout the repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: ${{ matrix.java }}
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Run Functional Tests"
+ env:
+ GITHUB_MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
+ run: >
+ ./gradlew ${{ matrix.gradle_task }}
+ --continue
+ --rerun-tasks
+ --stacktrace
+ -PgrailsIndy=${{ matrix.indy }}
+ -PonlyHibernate7Tests
+ -PskipCodeStyle
+ ${{ matrix.shard_arguments }}
+ neo4jFunctional:
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ name: "Neo4j Functional Tests (Java ${{ matrix.java }}, indy=${{ matrix.indy }})"
+ timeout-minutes: 180 # slowest run: 90 min
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ java: [ 21, 25 ]
+ indy: [ false ]
+ include:
+ - java: 21
+ indy: true
+ steps:
+ - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "๐ฅ Checkout the repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: ${{ matrix.java }}
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Run Functional Tests"
+ env:
+ GITHUB_MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
+ run: >
+ ./gradlew bootJar check
+ --continue
+ --rerun-tasks
+ --stacktrace
+ -PgrailsIndy=${{ matrix.indy }}
+ -PonlyNeo4jTests
+ -PskipCodeStyle
publishGradle:
if: github.repository_owner == 'apache' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
needs: [ buildGradle ]
@@@ -880,14 -566,14 +880,14 @@@
- name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
run: curl -s https://api.ipify.org
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
+ uses: actions/cache@v6.1.0
with:
# Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
# Keyed by branch version so each release branch maintains its own warm cache.
@@@ -947,14 -631,14 +947,14 @@@
- name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
run: curl -s https://api.ipify.org
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
+ uses: actions/cache@v6.1.0
with:
# Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
# Keyed by branch version so each release branch maintains its own warm cache.
@@@ -1013,14 -697,14 +1013,14 @@@
- name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
run: curl -s https://api.ipify.org
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
+ uses: actions/cache@v6.1.0
with:
# Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
# Keyed by branch version so each release branch maintains its own warm cache.
@@@ -1069,14 -753,14 +1069,14 @@@
- name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
run: curl -s https://api.ipify.org
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐ฅ Download Wrapper"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ uses: actions/download-artifact@v8.0.1
with:
name: apache-grails-wrapper-SNAPSHOT-bin
path: wrapper
@@@ -1126,12 -810,12 +1126,12 @@@
echo "NOW=${NOW}" >> $GITHUB_ENV
echo "Current date: ${NOW}"
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐๏ธ Restore dependency jar cache"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
+ uses: actions/cache@v6.1.0
with:
# Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
# Keyed by branch version so each release branch maintains its own warm cache.
diff --cc .github/workflows/groovy-snapshot-canary.yml
index 27336d370a,0000000000..fb1c913f47
mode 100644,000000..100644
--- a/.github/workflows/groovy-snapshot-canary.yml
+++ b/.github/workflows/groovy-snapshot-canary.yml
@@@ -1,196 -1,0 +1,196 @@@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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. You may obtain a copy of the License at
+#
+# https://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.
+
+name: "Groovy Snapshot Canary Build"
+on:
+ push:
+ branches:
+ - '[0-9]+.[0-9]+.x'
+ pull_request:
+ branches:
+ - '[0-9]+.[0-9]+.x'
+ workflow_dispatch:
+# queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+permissions:
+ contents: read
+jobs:
+ build_groovy:
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ name: "Build Groovy Snapshot"
+ runs-on: ubuntu-latest
+ outputs:
+ groovyVersion: ${{ steps.groovy-version.outputs.value }}
+ steps:
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ java-version: 21
+ distribution: liberica
+ - name: "๐ฅ Checkout Grails Core to fetch Gradle Plugin versions it uses"
+ # Sparse-checkout fetches only the two paths this job actually needs:
+ # - dependencies.gradle: parsed below to derive the matching Apache
+ # Groovy branch (GROOVY_<major>_<minor>_X) for this Grails branch.
+ # - .github/scripts/groovy-snapshot-canary-build.init.gradle: passed to
+ # ./gradlew --init-script when publishing Groovy to local Maven so
+ # the canary build reuses our Develocity / build-cache settings.
+ # Both paths must be listed here; otherwise the init-script step fails
+ # with "The specified initialization script ... does not exist."
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ with:
+ sparse-checkout-cone-mode: false
+ sparse-checkout: |
+ dependencies.gradle
+ .github/scripts/groovy-snapshot-canary-build.init.gradle
+ - name: "๐ Derive matching Apache Groovy branch from dependencies.gradle"
+ id: groovy-branch
+ run: |
+ # Extract the major and minor version of `groovy.version` declared in this
+ # branch's dependencies.gradle (e.g. '5.0.5' -> '5_0', '4.0.31' -> '4_0')
+ # and use it to pick the matching Apache Groovy development branch
+ # (GROOVY_<major>_<minor>_X).
+ # This keeps `7.0.x` PRs validating against Groovy 4 and `8.0.x` PRs
+ # validating against Groovy 5 without hard-coding the branch here.
+ GROOVY_MAJOR_MINOR=$(sed -nE "s/.*'groovy\.version'.*'([0-9]+)\.([0-9]+)\.[0-9]+'.*/\1_\2/p" dependencies.gradle | head -n 1)
+ if [ -z "$GROOVY_MAJOR_MINOR" ]; then
+ echo "::error::Could not determine Apache Groovy major/minor version from dependencies.gradle"
+ exit 1
+ fi
+ GROOVY_BRANCH="GROOVY_${GROOVY_MAJOR_MINOR}_X"
+ echo "Validating against Apache Groovy branch: $GROOVY_BRANCH"
+ echo "value=$GROOVY_BRANCH" >> $GITHUB_OUTPUT
+ rm dependencies.gradle
+ - name: "๐ฅ Checkout Apache Groovy (${{ steps.groovy-branch.outputs.value }})"
+ run: git clone --depth 1 https://github.com/apache/groovy.git -b ${{ steps.groovy-branch.outputs.value }} --single-branch
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-disabled: true # this workflow rebuilds Groovy each run; a Gradle home cache is not useful here
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Setup TestLens"
+ uses: testlens-app/setup-testlens@3f82d2dc6cd5c03f02ce5f7885a21195d85f8d14 # v1.9.3
+ - name: "๐ Store Groovy version to use when building Grails"
+ id: groovy-version
+ run: |
+ cd groovy
+ GROOVY_VERSION=$(cat gradle.properties | grep groovyVersion | cut -d\= -f2 | tr -d '[:space:]')
+ echo "Groovy version $GROOVY_VERSION stored"
+ echo "value=$GROOVY_VERSION" >> $GITHUB_OUTPUT
+ - name: "๐จ Publish Groovy to local maven repository (no docs)"
+ run: |
+ cd groovy
+ ./gradlew pTML -x groovydoc -x javadoc -x javadocAll -x groovydocAll -x asciidoc -x docGDK \
+ --init-script $GITHUB_WORKSPACE/.github/scripts/groovy-snapshot-canary-build.init.gradle
+ - name: "๐ค Share locally built Groovy artifacts with the build_grails job"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
++ uses: actions/upload-artifact@v7.0.1
+ with:
+ name: local-groovy-maven-repo
+ path: ~/.m2/repository/org/apache/groovy/
+ if-no-files-found: 'error'
+ retention-days: 1
+ build_grails:
+ needs: [build_groovy]
+ name: "Build Grails (shard ${{ matrix.shard_index }})"
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - shard_index: 0
+ gradle_task: build
+ - shard_index: 1
+ gradle_task: testShard
+ - shard_index: 2
+ gradle_task: testShard
+ runs-on: ubuntu-latest
+ steps:
+ - name: "โ๏ธ Maximize build space"
+ run: |
+ echo "-- Dotnet"
+ sudo rm -rf /usr/share/dotnet
+ echo "-- Android"
+ sudo rm -rf /usr/local/lib/android
+ echo "-- Haskell"
+ sudo rm -rf /opt/ghc
+ echo "-- CodeQL"
+ sudo rm -rf /opt/hostedtoolcache/CodeQL
+
+ df -h
+ - name: "๐ฅ Checkout project"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ java-version: 21
+ distribution: liberica
+ - name: "๐๏ธ Restore dependency jar cache"
+ id: dependency-cache
- uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache/restore@v6.1.0
+ with:
+ # Cache only downloaded dependency jars and wrapper distributions, never Grails build outputs.
+ # Keyed by branch version so each release branch maintains its own warm cache.
+ path: |
+ ~/.gradle/caches/modules-2
+ ~/.gradle/wrapper
+ key: gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-${{ hashFiles('**/dependencies.gradle', '**/gradle-wrapper.properties') }}
+ restore-keys: |
+ gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-
+ - name: "๐๏ธ Cache the mongod the embedded server runs"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache@v6.1.0
+ with:
+ # Separate from the dependency jars, for the reasons given on the same step in
+ # gradle.yml. The version is the one -PmongodbContainerVersion asks for below.
+ path: ~/.embedmongo
+ key: embedmongo-${{ runner.os }}-8.0
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-disabled: true # dependency jars are cached by the explicit branch-keyed step above
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Setup TestLens"
+ uses: testlens-app/setup-testlens@3f82d2dc6cd5c03f02ce5f7885a21195d85f8d14 # v1.9.3
+ - name: "๐ฅ Fetch the locally built Groovy artifacts"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
++ uses: actions/download-artifact@v8.0.1
+ with:
+ name: local-groovy-maven-repo
+ path: ~/.m2/repository/org/apache/groovy
+ - name: 'Swap Groovy Version'
+ run: |
+ sed -i "/['\"]groovy\.version['\"][[:space:]]*:/c\'groovy.version':'${{ needs.build_groovy.outputs.groovyVersion }}'," dependencies.gradle
+ - name: "๐จ Build and test Grails using the locally built Groovy snapshot"
+ # -PmongodbContainerVersion names the MongoDB the specifications bring themselves: a
+ # container for most of them, an embedded server for the ones that need a replica set. It
+ # keeps those two on the same release. This workflow validates Grails against a Groovy
+ # snapshot rather than against a range of MongoDB releases, which is gradle.yml's matrix.
+ run: >
+ ./gradlew ${{ matrix.gradle_task }}
+ --continue
+ --stacktrace
+ -PskipCodeStyle
+ -PmaxTestParallel=3
+ -PtestShardCount=3
+ -PtestShardIndex=${{ matrix.shard_index }}
+ -PmongodbContainerVersion=8.0
+ env:
+ GRAILS_INCLUDE_MAVEN_LOCAL: true
+ - name: "๐๏ธ Save dependency jar cache"
+ if: ${{ success() && matrix.shard_index == 0 && steps.dependency-cache.outputs.cache-hit != 'true' }}
- uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
++ uses: actions/cache/save@v6.1.0
+ with:
+ path: |
+ ~/.gradle/caches/modules-2
+ ~/.gradle/wrapper
+ key: gradle-deps-${{ runner.os }}-${{ github.base_ref || github.ref_name }}-${{ hashFiles('**/dependencies.gradle', '**/gradle-wrapper.properties') }}
diff --cc .github/workflows/rat.yml
index 1aeb13732d,46e90f38a4..15523bfc30
--- a/.github/workflows/rat.yml
+++ b/.github/workflows/rat.yml
@@@ -33,12 -35,12 +33,12 @@@ jobs
runs-on: ubuntu-latest
steps:
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: 21
- name: "๐ Setup Gradle"
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
diff --cc .github/workflows/sitemesh2.yml
index 8b597672ec,0000000000..404be46dee
mode 100644,000000..100644
--- a/.github/workflows/sitemesh2.yml
+++ b/.github/workflows/sitemesh2.yml
@@@ -1,126 -1,0 +1,126 @@@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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. You may obtain a copy of the License at
+#
+# https://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.
+
+# SiteMesh 3 is the default GSP layout engine. This separate lane exercises the
+# legacy SiteMesh 2 (grails-layout) path so that regressions affecting apps that
+# still rely on it are caught. Setting SITEMESH2_TESTING_ENABLED=true flips the
+# starter, the uber unit suite and the functional test examples back onto
+# :grails-layout instead of :grails-sitemesh3.
+name: "SiteMesh 2 Compatibility"
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 6 * * 1' # Weekly, Mondays 06:00 UTC
+ push:
+ branches:
+ - '[0-9]+.[0-9]+.x'
+ - '8.0.x-hibernate7.*'
+ paths:
+ - 'grails-gsp/**'
+ - 'grails-test-suite-uber/**'
+ - 'grails-test-examples/**'
+ - 'grails-dependencies/**'
+ - '.github/workflows/sitemesh2.yml'
+ pull_request:
+ paths:
+ - 'grails-gsp/**'
+ - 'grails-test-suite-uber/**'
+ - 'grails-test-examples/**'
+ - 'grails-dependencies/**'
+ - '.github/workflows/sitemesh2.yml'
+# Queue jobs - cancel in-progress PR runs when new commits pushed, but allow branch builds to complete
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+ coreTests:
+ name: "SiteMesh 2 Core Tests (Java ${{ matrix.java }})"
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ strategy:
+ fail-fast: false
+ matrix:
+ java: [ 21 ]
+ runs-on: ubuntu-24.04
+ env:
+ SITEMESH2_TESTING_ENABLED: 'true'
+ steps:
+ - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: ${{ matrix.java }}
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Setup TestLens"
+ uses: testlens-app/setup-testlens@3f82d2dc6cd5c03f02ce5f7885a21195d85f8d14 # v1.9.3
+ - name: "๐จ Run Core Tests against SiteMesh 2"
+ # --rerun-tasks forces the tests to execute against the grails-layout
+ # classpath instead of reusing outputs cached from a SiteMesh 3 build.
+ run: >
+ ./gradlew build
+ --continue
+ --rerun-tasks
+ --stacktrace
+ -PonlyCoreTests
+ -PskipCodeStyle
+ functionalTests:
+ name: "SiteMesh 2 Functional Tests (Java ${{ matrix.java }}, indy=${{ matrix.indy }})"
+ if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }}
+ strategy:
+ fail-fast: false
+ matrix:
+ java: [ 21 ]
+ indy: [ false ]
+ runs-on: ubuntu-24.04
+ env:
+ SITEMESH2_TESTING_ENABLED: 'true'
+ steps:
+ - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: ${{ matrix.java }}
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Setup TestLens"
+ uses: testlens-app/setup-testlens@3f82d2dc6cd5c03f02ce5f7885a21195d85f8d14 # v1.9.3
+ - name: "๐ Run Functional Tests against SiteMesh 2"
+ # --rerun-tasks forces the tests to execute against the grails-layout
+ # classpath instead of reusing outputs cached from a SiteMesh 3 build.
+ run: >
+ ./gradlew bootJar check
+ --continue
+ --rerun-tasks
+ --stacktrace
+ -PgrailsIndy=${{ matrix.indy }}
+ -PonlyFunctionalTests
+ -PskipCodeStyle
+ -PskipHibernate5Tests
+ -PskipHibernate7Tests
+ -PskipMongodbTests
diff --cc .github/workflows/validate-actions.yml
index a459724633,0000000000..9891224f26
mode 100644,000000..100644
--- a/.github/workflows/validate-actions.yml
+++ b/.github/workflows/validate-actions.yml
@@@ -1,59 -1,0 +1,59 @@@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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. You may obtain a copy of the License at
+#
+# https://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.
+
+name: "Validate GitHub Actions"
+on:
+ push:
+ branches:
+ - '[0-9]+.[0-9]+.x'
+ - '8.0.x-hibernate7.*'
+ paths:
+ - '.github/workflows/**'
+ - 'build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/ValidateActionsTask.groovy'
+ - 'build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsValidateActionsPlugin.groovy'
+ pull_request:
+ paths:
+ - '.github/workflows/**'
+ - 'build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/ValidateActionsTask.groovy'
+ - 'build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsValidateActionsPlugin.groovy'
+ schedule:
+ - cron: '0 6 * * 1' # weekly Monday 06:00 UTC โ catches ASF delisting a pinned SHA
+ workflow_dispatch:
+permissions:
+ contents: read
+# queue jobs - cancel in-progress PR runs when new commits are pushed, but allow branch builds to complete
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+ validate:
+ name: "Validate Actions"
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
++ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
++ uses: actions/setup-java@v6.0.1
+ with:
+ distribution: liberica
+ java-version: 21
+ - name: "๐ Setup Gradle"
+ uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
+ with:
+ cache-provider: basic # 'basic' uses the MIT-licensed, open-source cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
+ develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ - name: "๐ Validate Actions against the ASF approved list"
+ run: ./gradlew validateActions
+ timeout-minutes: 10
diff --cc .github/workflows/vulnerability-scan.yml
index 4ae478ffe1,b5a715a283..c924e386aa
--- a/.github/workflows/vulnerability-scan.yml
+++ b/.github/workflows/vulnerability-scan.yml
@@@ -44,27 -44,12 +44,27 @@@ jobs
contents: read
steps:
- name: "๐ฅ Checkout repository"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Determine Java version from .sdkmanrc"
+ # Read the JDK the build targets out of the file that already declares it, so a
+ # baseline bump does not leave a stale literal behind here. Only the major is kept:
+ # a scan is outside the reproducible-build surface, so CI takes the runner's current
+ # release of that major rather than pinning the patch version.
+ id: sdkmanrc
+ run: |
+ set -euo pipefail
+ sdkman_java=$(grep -E '^java=' .sdkmanrc | cut -d= -f2 || true)
+ if [ -z "${sdkman_java}" ]; then
+ echo "โ Could not determine the java version from .sdkmanrc" >&2
+ exit 1
+ fi
+ echo "Scanning with JDK ${sdkman_java%%.*} (from .sdkmanrc: ${sdkman_java})"
+ echo "java-version=${sdkman_java%%.*}" >> "$GITHUB_OUTPUT"
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: ${{ steps.sdkmanrc.outputs.java-version }}
- name: "๐ Setup Gradle"
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
@@@ -98,27 -83,12 +98,27 @@@
pull-requests: write
steps:
- name: "๐ฅ Checkout pull request"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ uses: actions/checkout@v7.0.1
+ - name: "โ๏ธ Determine Java version from .sdkmanrc"
+ # Read the JDK the build targets out of the file that already declares it, so a
+ # baseline bump does not leave a stale literal behind here. Only the major is kept:
+ # a scan is outside the reproducible-build surface, so CI takes the runner's current
+ # release of that major rather than pinning the patch version.
+ id: sdkmanrc
+ run: |
+ set -euo pipefail
+ sdkman_java=$(grep -E '^java=' .sdkmanrc | cut -d= -f2 || true)
+ if [ -z "${sdkman_java}" ]; then
+ echo "โ Could not determine the java version from .sdkmanrc" >&2
+ exit 1
+ fi
+ echo "Scanning with JDK ${sdkman_java%%.*} (from .sdkmanrc: ${sdkman_java})"
+ echo "java-version=${sdkman_java%%.*}" >> "$GITHUB_OUTPUT"
- name: "โ๏ธ Setup JDK"
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ uses: actions/setup-java@v6.0.1
with:
distribution: liberica
- java-version: 17
+ java-version: ${{ steps.sdkmanrc.outputs.java-version }}
- name: "๐ Setup Gradle"
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
diff --cc AGENTS.md
index 6c7eaabb4e,4cb8a00a97..4fb8771ceb
--- a/AGENTS.md
+++ b/AGENTS.md
@@@ -51,11 -51,8 +51,11 @@@ export GRADLE_OPTS="-Dorg.gradle.jvmarg
8. **No internal APIs in docs** - Only document public APIs; never reference internal or package-private classes and methods in user-facing documentation
9. **Test via public APIs** - Tests must exercise behavior through the same APIs an end user calls; never invoke internal implementations, package-private methods, or bypass the public surface directly
10. **Always review and extend tests** - Review existing unit and functional tests before making changes; every code change must include new or enhanced tests that cover the affected behavior
-11. **The BOM must manage the latest version** - `validateDependencyVersions` enforces that the BOM (`dependencies.gradle`) manages a version `>=` every transitively-resolved version. When it fails, **bump the version in `dependencies.gradle`** so the BOM wins โ never silence it with `allowedBomOverrides` or an exclusion unless there is an explicit, documented conflict or an agreed-upon workaround. See [Dependency Management](#dependency-management).
-12. **GitHub Actions must use ASF-approved references** - Every third-party action SHA must appear in the ASF allowlist; `actions/*` use the full tag of their latest release and `apache/*` use version or branch references, never SHAs. See [GitHub Actions](#github-actions).
+11. **Every code touch must update all tests for the changed class** - When a class is modified, find and update every test that covers it โ unit, integration, and TCK. Do not leave any existing test out of sync with the new code.
+12. **Clean violations before commit** - Before every automated commit, run `./gradlew clean aggregateViolations :grails-test-report:check --continue` from the root and ensure that `build/reports/violations/CHECKSTYLE_VIOLATIONS.md`, `build/reports/violations/CODENARC_VIOLATIONS.md`, `build/reports/violations/PMD_VIOLATIONS.md`, and `build/reports/violations/SPOTBUGS_VIOLATIONS.md` report no issues. Also review the test result reports under `grails-test-report/build/reports/tests/` and [...]
+13. **Mandatory test coverage** - Any class touched in a commit MUST be covered with tests that verify all behavior. You must run ALL tests in the affected module(s) and ensure they pass before committing.
+14. **The BOM must manage the latest version** - `validateDependencyVersions` enforces that the BOM (`dependencies.gradle`) manages a version `>=` every transitively-resolved version. When it fails, **bump the version in `dependencies.gradle`** so the BOM wins โ never silence it with `allowedBomOverrides` or an exclusion unless there is an explicit, documented conflict or an agreed-upon workaround. See [Dependency Management](#dependency-management).
- 15. **GitHub Actions must use ASF-approved pins** - Every third-party action SHA must appear in the ASF allowlist. See [GitHub Actions](#github-actions).
++15. **GitHub Actions must use ASF-approved references** - Every third-party action SHA must appear in the ASF allowlist; `actions/*` use the full tag of their latest release and `apache/*` use version or branch references, never SHAs. See [GitHub Actions](#github-actions).
## Available Skills
diff --cc grails-data-hibernate5/docs/build.gradle
index 78cac8df9a,5ec29c3f35..f7556e25a4
--- a/grails-data-hibernate5/docs/build.gradle
+++ b/grails-data-hibernate5/docs/build.gradle
@@@ -71,18 -72,23 +71,22 @@@ tasks.named('asciidoctor', AsciidoctorT
}
it.attributes = [
- 'experimental': 'true',
- 'compat-mode': 'true',
- 'toc': 'left',
- 'icons': 'font',
- 'reproducible': '',
- 'version': projectVersion,
- 'pluginVersion': projectVersion,
- 'groupId': project.group,
- 'artifactId': project.name,
+ 'experimental' : 'true',
+ 'compat-mode' : 'true',
+ 'toc' : 'left',
+ 'icons' : 'font',
+ 'reproducible' : '',
+ 'version' : projectVersion,
+ 'pluginVersion' : projectVersion,
+ 'groupId' : project.group,
+ 'artifactId' : project.name,
'migrationPluginExamplesDir': project.layout.projectDirectory.dir('src/docs/asciidoc/databaseMigration').asFile.relativePath(rootProject.findProject(':grails-data-hibernate5-dbmigration').layout.projectDirectory.asFile),
- 'migrationPluginGroupId': rootProject.findProject(':grails-data-hibernate5-dbmigration').group,
- 'migrationPluginArtifactId': rootProject.findProject(':grails-data-hibernate5-dbmigration').name,
- 'liquibaseHibernate5Version': liquibaseHibernate5Version,
+ 'migrationPluginGroupId' : rootProject.findProject(':grails-data-hibernate5-dbmigration').group,
+ 'migrationPluginArtifactId' : rootProject.findProject(':grails-data-hibernate5-dbmigration').name,
+ // Every file under the source directory is also converted on its own, including the ones that only
+ // exist to be included by index.adoc. Their headings start at the depth they are included at, so they
+ // are converted as fragments. The '@' lets index.adoc switch this back off to keep its own checks.
- 'fragment': '@'
++ 'fragment' : '@'
]
}