Skip to content

chore: migrate to Grails 8.0.0-M5 and re-sync grails-plugin-template - #50

Merged
sbglasius merged 2 commits into
8.0.xfrom
chore/grails-8.0.0-M5
Aug 22, 2026
Merged

sbglasius merged 2 commits into
8.0.xfrom
chore/grails-8.0.0-M5

Conversation

@sbglasius

Copy link
Copy Markdown
Member

Moves the plugin onto Grails 8.0.0-M5, and brings the repo back up to date with
grails-plugin-template. Two commits, reviewable independently.

1. chore: migrate to Grails 8.0.0-M5

Grails 8.0.0-M5, Groovy 5.0.8, Spring Boot 4.1, Java 21, Gradle 9.6.0 — the toolchain
versions are taken from grails-core at v8.0.0-M5. CI derives its JDK from .sdkmanrc,
so no workflow change was needed.

Breakages found and fixed along the way:

  • springDependencyManagement = false is deprecated in Grails 8 → bom = null.
  • docs failed outright on Gradle 9: the root project's aggregateGroovyApiDoc resolved
    :greenmail's compile and groovy classpaths, which is no longer allowed across projects.
    The Groovydoc task now lives on the plugin project; the output layout is unchanged.
  • jansi is no longer in the Grails BOM, so the unversioned dependency broke configuration
    of the example app.
  • Both grails.mime blocks are gone. Grails 8 ships exactly the list they declared, and
    honours the Accept header for every user agent now.
  • Logback 1.5 deprecated conversionRule converterClass → class.

Also fixes a pre-existing bug the migration surfaced: :app1:bootRun failed with
NoClassDefFoundError: grails.artefact.controller.RestResponder. The plugin's controller
carries the RestResponder trait, but the example app hand-lists Grails modules and omitted
grails-rest-transforms — only grails-testing-support-web supplied it, so the tests
passed while the app could not boot. This fails identically on 7.0.x; it is not a
migration regression.

2. chore: sync common files from grails-plugin-template

Replays the template's own files-sync.yml logic rather than hand-picking files.

Brings in verify-repository.groovy and the verify CI job that gates the build on it;
config.example-app applying config.code-coverage (so the example app's integration
tests now feed the aggregated coverage report); config.publish self-applying
config.project-metadata; config.publish-root taking the Maven group from a new
projectGroup property instead of hardcoding it; restored "do not edit directly" headers;
and robustness fixes in the update-versions / update-contributors scripts.

ci.yml also stops passing -PskipCodeStyle on the build-with-tests step, so CodeNarc
and Checkstyle now run in CI for the first time
. Both are clean.

Two deliberate divergences from a literal sync:

  • stale.yml → local-stale.yml. It is not a template workflow, and the workflow sync is
    an rsync --delete that spares only the local-*.yml names.
  • gradle/ is not synced — it would downgrade the wrapper from 9.6.0 back to the
    template's 8.14.4.

One real breakage the sync caused

config.example-app pulling in config.code-coverage made :app1:integrationTest fail
with Expected configuration ':app1:jacocoAgent' to contain exactly one file, however, it contains no files. The jacoco plugin contributes its agent and ant coordinates through
defaultDependencies, which Gradle skips once a configuration has any dependency of its
own — and Grails 8 applies the Grails BOM to every declarable configuration,
jacocoAgent included, so the fallback never fires. The plugin project was unaffected only
because config.grails-plugin sets bom = null. config.code-coverage now declares the
coordinates outright.

Verification

Green from clean on JDK 21 / Gradle 9.6.0:

  • ./gradlew build docs — 12 unit tests, 11 integration tests, CodeNarc and Checkstyle
  • ./gradlew publishToMavenLocal — still publishes io.github.gpc:greenmail
  • groovy .github/scripts/verify-repository.groovy — no errors
  • :app1:bootRun on a real port, hitting /greenmail/list (200 text/html) and
    /greenmail/list.json (200 application/json)
  • Booted once with spring-boot-properties-migrator attached: no warnings. It was removed
    again before this branch was committed.

Follow-ups, not in this PR

  • Three fixes want upstreaming to grails-plugin-template: bom = null, the Gradle 9
    config.docs fix, and the jacoco defaultDependencies fix. Every template repo will hit
    all three the moment the template moves to Grails 8. The jacoco one is the most urgent.
  • files-sync.yml rsyncs gradle/ unconditionally. It calls lock_excludes for
    .github/workflows, .github/scripts and build-logic, but not for gradle/ — so the
    .lock convention cannot protect the wrapper. Once greenmail is registered in the
    template's projects.yml, the first sync would silently downgrade its wrapper and break
    the build.
  • The .lock files here pin what must not come from a Grails 7 template. All except
    CONTRIBUTING.md.lock are temporary and should be deleted once the template is on
    Grails 8.
  • CONTRIBUTING.md links the licence as LICENSE, but the file is LICENSE.txt. The
    template's generic copy gets this right; ours is pinned, so it keeps the dead link.
  • .agents/skills/enhance-plugin-with-template/ is stale here and arguably template-only —
    the sync neither updates nor removes it.

Moves the build onto the Grails 8 platform: Grails 8.0.0-M5, Groovy 5.0.8,
Spring Boot 4.1, Java 21 and Gradle 9.6.0.

- `.sdkmanrc`, the Gradle wrapper and the docs are bumped to the Grails 8
  toolchain. CI derives its JDK from `.sdkmanrc`, so no workflow change is
  needed; dependabot's Gradle ceiling moves from 9 to 10.
- `config.grails-plugin` replaces the removed `springDependencyManagement`
  flag with `bom = null`, its Grails 8 equivalent.
- `config.docs` registers the aggregate Groovydoc task on the plugin project.
  It resolves that project's compile and groovy classpaths, which Gradle 9 no
  longer allows from a task owned by the root project.
- The `grails.mime` blocks are dropped from both `application.yml` files.
  Grails 8 supplies exactly these defaults and honours the Accept header for
  every user agent.
- `jansi` is no longer managed by the Grails BOM, so it is dropped from the
  example app.
- The example app gains `grails-rest-transforms`. The plugin's controller is
  compiled with the RestResponder trait, so it is needed to boot the app, not
  just to run its tests. This was already broken before the migration; only
  the integration tests supplied the module, via `grails-testing-support-web`.
- The plugin's `logback.xml` uses `conversionRule class`, replacing the
  attribute logback 1.5 deprecated.
Replays the template's files-sync.yml against the current
grails-plugin-template, which greenmail had drifted well behind.

What the sync brings in:

- `.github/scripts/verify-repository.groovy` and the `verify` job that runs it
  ahead of the build in `ci.yml`. It reports warnings only here, no errors.
- `ci.yml` no longer passes `-PskipCodeStyle` on the build-with-tests step, so
  CodeNarc and Checkstyle now run in CI. Both are clean.
- `config.example-app` applies `config.code-coverage`, so the example app's
  integration tests now contribute to the aggregated coverage report.
- `config.publish` applies `config.project-metadata` itself, and
  `config.publish-root` takes the Maven group from a new `projectGroup`
  property rather than hardcoding it. `plugin` and `docs` follow suit.
  `verify-repository.groovy` requires that property too.
- Restored the "do not edit directly" headers on the convention plugins that
  had lost them, plus update-versions/update-contributors robustness fixes.

Two deliberate divergences from a literal sync:

- `stale.yml` becomes `local-stale.yml`. It is not a template workflow, and the
  workflow sync is an rsync `--delete` that spares only the `local-*.yml` names.
- `gradle/` is not synced. It would downgrade the wrapper from 9.6.0 to the
  template's 8.14.4, and it is the one synced tree the `.lock` convention does
  not cover.

The `.lock` files pin what must not come from a Grails 7 template: `.sdkmanrc`,
`gradlew`, `gradlew.bat`, `dependabot.yml`, `config.grails-plugin`,
`config.docs` and `config.code-coverage`. All but `CONTRIBUTING.md` are
temporary and should be dropped once the template itself is on Grails 8.

`config.code-coverage` needed a fix of its own to survive the sync: the jacoco
plugin contributes its agent and ant coordinates through `defaultDependencies`,
which Gradle skips once a configuration has any dependency of its own. Grails 8
applies the Grails BOM to every declarable configuration, `jacocoAgent`
included, so the fallback never fired and `:app1:integrationTest` failed with an
empty agent configuration. The coordinates are now declared outright.
@sbglasius
sbglasius merged commit b30615d into 8.0.x Aug 22, 2026
5 checks passed
@sbglasius
sbglasius deleted the chore/grails-8.0.0-M5 branch August 22, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant