Skip to content

Automate configuration metadata for @ConfigurationProperties modules - #16047

Open
jamesfredley wants to merge 18 commits into
8.0.xfrom
feature/automated-configuration-metadata
Open

Automate configuration metadata for @ConfigurationProperties modules#16047
jamesfredley wants to merge 18 commits into
8.0.xfrom
feature/automated-configuration-metadata

Conversation

@jamesfredley

@jamesfredley jamesfredley commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What this does

Closes #15469 for the five modules that already use @ConfigurationProperties:

  • grails-cache
  • grails-databinding
  • grails-views-gson
  • grails-views-markup
  • grails-web-url-mappings (CORS owner)

Each publishes standard Spring Boot metadata at META-INF/spring-configuration-metadata.json during the module build. IDEs, config-report, and the Application Properties reference consume that resource.

This is not Spring Boot's spring-boot-configuration-processor. That approach was tried in #15566 and closed because Java annotation processors break incremental Groovy compilation. This PR uses a Grails-owned path instead:

  1. Groovy: a semantic-analysis AST transform embeds a private synthetic metadata payload on each compiled @ConfigurationProperties class (no shared processor output).
  2. Java: ASM reads compiled bytecode without classloading.
  3. Build: a cacheable Gradle task merges class payloads after compilation, applies curated overlays, and writes one standard metadata file into resources/jars.

Migration details

Module Change
Cache / Data Binding / JSON Views Hand-written spring-configuration-metadata.json renamed to additional-spring-configuration-metadata.json (overlay). Overlay values win for matching identities.
Markup Views No prior metadata file on 8.0.x; generation now publishes bindable Markup Views properties.
CORS Moved from grails-web-core into grails-web-url-mappings with GrailsCorsConfiguration. Non-CORS web-core metadata stays hand-maintained in grails-web-core.

Generated defaults only include compile-time constants. Dynamic Groovy defaults are omitted unless an overlay supplies an authoritative value.

Compatibility check (against origin/8.0.x)

Re-verified on exact PR HEAD after regenerating metadata:

Source Result
Cache 1 group / 6 properties preserved, no changes
Data Binding 1 group / 5 properties preserved, no changes
JSON Views 1 group / 9 properties preserved, no changes; +9 inferred template/base properties
CORS 1 group / 9 properties preserved and relocated to URL Mappings, no changes
Web-core non-CORS 10 groups / 24 properties still present in grails-web-core
Markup Views New generated group/properties (no prior 8.0.x file)

Each migrated jar contains exactly one standard metadata resource plus any curated additional metadata file.

Scope notes

  • Completes issue Phase 1 for the five existing @ConfigurationProperties modules.
  • Does not migrate Groovy-DSL-only config (e.g. Spring Security DefaultSecurityConfig, general application.groovy merging). Those remain later work.
  • Immutable constructor-bound properties are supported when Boot-compatible constructor selection applies; curated overlays remain the fallback for non-inferable metadata.

Verification

Local:

  • :grails-configuration-metadata:test + codeStyle
  • full ConfigurationMetadataPluginSpec (clean, incremental, edit, delete, overlay, duplicate, immutable, generic-constructor safety)
  • tests for Cache, Data Binding, JSON Views, Markup Views, URL Mappings, and affected Web Core
  • ConfigReportCommandSpec
  • :grails-doc:publishGuide -x aggregateGroovydoc
  • clean Checkstyle / CodeNarc / PMD / SpotBugs aggregates
  • semantic zero-loss comparison + jar resource inspection

CI on this PR: core builds (Linux/macOS), style/analysis/RAT/CodeQL/coverage, Forge, functional, security, Redis, MongoDB, and Hibernate suites are green. A few long jobs (Windows core, joint Groovy validation, selected functional reruns) were still finishing at description update time.

Commits

  1. Compiler module + AST transform
  2. Build-logic Gradle plugin + TestKit
    3-7. Per-module migrations (cache, databinding, gson, markup, URL mappings/CORS)
    8-9. Docs reference inputs + guide prose
  3. Immutable constructor-bound metadata support

Stacked follow-up

#16048 is stacked on this PR and should be reviewed and merged afterward. It handles Groovy DSL parsing and the remaining in-repository metadata migrations tracked by #15469. This PR remains limited to the five existing @ConfigurationProperties modules and the shared generator correctness fixes required by its review; #16048 does not replace those fixes.

Assisted-by: opencode:gpt-5.6-sol
Assisted-by: opencode:gpt-5.6-sol
Assisted-by: opencode:gpt-5.6-sol
Assisted-by: opencode:gpt-5.6-sol
Assisted-by: opencode:gpt-5.6-sol
Assisted-by: opencode:gpt-5.6-sol
Assisted-by: opencode:gpt-5.6-sol
Assisted-by: opencode:gpt-5.6-sol
Assisted-by: opencode:gpt-5.6-sol
Copilot AI review requested due to automatic review settings July 23, 2026 20:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new build-time pipeline to generate Spring Boot configuration metadata from compiled Groovy/Java @ConfigurationProperties classes, merging in curated additional-spring-configuration-metadata.json overlays and wiring the results into the docs config reference generation.

Changes:

  • Adds grails-configuration-metadata compiler module with a Groovy SEMANTIC_ANALYSIS AST transformation to embed deterministic metadata payloads in compiled Groovy configuration classes.
  • Adds a Gradle build-logic plugin (org.apache.grails.buildsrc.configuration-metadata) that scans compiled bytecode (ASM), merges curated overlays, and publishes a single standard META-INF/spring-configuration-metadata.json resource per jar.
  • Migrates existing curated metadata to additional-spring-configuration-metadata.json, relocates CORS metadata ownership to URL Mappings, and expands the docs config-reference pipeline inputs.

Reviewed changes

Copilot reviewed 19 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
settings.gradle Includes new grails-configuration-metadata module in the multi-project build.
gradle/publish-root-config.gradle Publishes the new grails-configuration-metadata module.
dependencies.gradle Adds ASM to the build BOM dependencies for bytecode scanning.
build-logic/plugins/build.gradle Adds ASM dependency and registers the new configuration metadata Gradle plugin.
build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/ConfigurationMetadataPlugin.groovy Implements bytecode scanning + overlay merge + deterministic metadata output task wired into processResources.
build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/ConfigurationMetadataPluginSpec.groovy TestKit coverage for clean/incremental/edit/deletion/overlay/duplicates/no-overlay behavior.
grails-configuration-metadata/build.gradle New compiler module build definition and test dependencies.
grails-configuration-metadata/src/main/groovy/org/apache/grails/configuration/metadata/ConfigurationMetadataTransformation.groovy Groovy AST transform embedding per-class metadata payloads (constant defaults only).
grails-configuration-metadata/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation Registers the global AST transformation.
grails-configuration-metadata/src/test/groovy/org/apache/grails/configuration/metadata/ConfigurationMetadataTransformationSpec.groovy Unit tests validating payload shape, defaults policy, and reserved-field collision handling.
grails-cache/build.gradle Applies the new configuration metadata build plugin.
grails-cache/src/main/resources/META-INF/additional-spring-configuration-metadata.json Adds curated cache metadata overlay for merge with generated metadata.
grails-databinding/build.gradle Applies the new configuration metadata build plugin.
grails-databinding/src/main/resources/META-INF/additional-spring-configuration-metadata.json Adds curated data binding metadata overlay.
grails-views-gson/build.gradle Applies the new configuration metadata build plugin.
grails-views-gson/src/main/resources/META-INF/additional-spring-configuration-metadata.json Adds curated JSON Views metadata overlay.
grails-views-markup/build.gradle Applies the new configuration metadata build plugin.
grails-web-url-mappings/build.gradle Applies the new configuration metadata build plugin.
grails-web-url-mappings/src/main/resources/META-INF/additional-spring-configuration-metadata.json Adds curated URL Mappings/CORS metadata overlay (migrated from web-core).
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json Removes CORS group/properties now owned by URL Mappings.
grails-doc/src/en/guide/conf/config.adoc Documents how Grails produces/overlays configuration metadata and its defaults policy.
grails-doc/build.gradle Adds migrated module jars as inputs to the configuration reference generation pipeline.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.04918% with 42 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.7471%. Comparing base (59046dd) to head (b770e0b).

Files with missing lines Patch % Lines
...etadata/ConfigurationMetadataTransformation.groovy 77.0492% 13 Missing and 29 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #16047        +/-   ##
==================================================
+ Coverage     54.7156%   54.7471%   +0.0315%     
- Complexity      20402      20483        +81     
==================================================
  Files            2098       2099         +1     
  Lines          100931     101114       +183     
  Branches        17900      17951        +51     
==================================================
+ Hits            55225      55357       +132     
- Misses          37842      37864        +22     
- Partials         7864       7893        +29     
Files with missing lines Coverage Δ
...ails/plugin/json/view/JsonViewConfiguration.groovy 77.7778% <ø> (ø)
...etadata/ConfigurationMetadataTransformation.groovy 77.0492% <77.0492%> (ø)

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jamesfredley jamesfredley moved this to In Progress in Apache Grails Jul 23, 2026
@jamesfredley jamesfredley self-assigned this Jul 23, 2026
@jamesfredley jamesfredley added this to the grails:8.0.0-RC1 milestone Jul 23, 2026
@jamesfredley jamesfredley changed the title Automate configuration metadata generation Automate configuration metadata for @ConfigurationProperties modules Jul 23, 2026
@jamesfredley jamesfredley moved this from In Progress to Todo in Apache Grails Jul 24, 2026
@jdaugherty

Copy link
Copy Markdown
Contributor

We had discussed creating a custom solution and not requiring ConfigurationProperties in the meeting, why the reversal here now?

@jdaugherty jdaugherty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had AI review this and then iterated with the review. I think the @Delegate finding is rather important. Review follows.

Read through the compiler module, the Gradle plugin and the five module migrations, then ran generateConfigurationMetadata for all five modules plus :grails-doc:generateConfigReference on this branch to diff the actual output. The mechanism works: the ASM path and the Groovy payload path both produce correct metadata for Java and Groovy config classes, and cache / data binding / JSON views / CORS come through with no loss. The TestKit spec covers clean, incremental, edit, delete and overlay behaviour well.

One thing I would want resolved before this goes in: the Application Properties reference regresses. 33 of 187 rows come out with an empty description and an empty default (24 grails.views.markup.*, 9 newly inferred grails.views.json.*). Every other row in that file has a description, so this PR introduces all 33.

The remaining comments are about how quietly the generated half can drift from what Boot actually binds (the @Delegate blind spot and the isNested heuristic), how thinly the transform itself is covered, and a question about publishing the compiler module.

This review is on the implementation as written; it does not address the separate open question about whether the approach should require @ConfigurationProperties.

Comment thread grails-doc/build.gradle
project(':grails-data-mongodb').tasks.named('jar'),
project(':grails-views-gson').tasks.named('jar')
project(':grails-views-gson').tasks.named('jar'),
project(':grails-views-markup').tasks.named('jar'),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wiring these two jars into generateConfigReference publishes the generated metadata straight into the Application Properties reference, and grails-views-markup has no curated overlay. Generating the reference from this branch:

  • 33 of 187 rows now have an empty Description and an empty Default: all 24 grails.views.markup.*, plus the 9 newly inferred grails.views.json.* entries (baseTemplateClass, cache, enableReloading, extension, packageImports, packageName, staticImports, templatePath, useAbsoluteLinks). Every pre-existing row in that file has a description, so all 33 blanks are new.
  • The markup rows land inside the existing "Views & GSP" section, interleaved with fully documented grails.views.gsp.* rows, so they read as holes in an otherwise complete table.

Every one of these is settable, so every one needs documenting. Can we add grails-views-markup/src/main/resources/META-INF/additional-spring-configuration-metadata.json with a group description plus a description and default for each of the 24, and descriptions for the 9 new JSON Views entries, before these jars feed the reference?

Comment thread grails-configuration-metadata/build.gradle Outdated
Cover inherited and excluded setters, constructor selection, deferred
prefixes, complex type names, JSON escaping, interfaces, and unprefixed
configuration properties.

Assisted-by: opencode:gpt-5.6-sol
Preserve both build-logic plugin registrations and consolidate their shared
ASM dependency while integrating the current base branch.

Assisted-by: opencode:gpt-5.6-sol
Add curated descriptions and authoritative defaults for the Markup and JSON
Views properties included in the generated application reference.

Assisted-by: opencode:gpt-5.6-sol
Make duplicate class rejection explicit and retain legal repeated groups by
their source provenance with deterministic overlay merging.

Assisted-by: opencode:gpt-5.6-sol
Remove publication and BOM wiring for the build-only AST transform module,
and drop its unused production Spring Boot dependency.

Assisted-by: opencode:gpt-5.6-sol
Warn when delegated properties require explicit metadata and limit nested
expansion to inner or annotated JavaBean properties.

Assisted-by: opencode:gpt-5.6-sol
jamesfredley and others added 2 commits August 6, 2026 12:14
Keep unannotated top-level constructor properties scalar while preserving
inner and explicitly annotated nested configuration properties.

Assisted-by: opencode:gpt-5.6-sol
GitHub's cached mergeable state was stale, computed against an older
8.0.x commit than current tip. This branch merges cleanly against the
current 8.0.x tip with no textual conflicts; verified by running the
full test suites for grails-configuration-metadata, build-logic, and
the five migrated modules (cache, databinding, gson, markup views,
url-mappings) with no new failures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@testlens-app

testlens-app Bot commented Aug 29, 2026

Copy link
Copy Markdown

🚨 TestLens detected 1 failed test 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

CI / Functional Tests (Java 21, indy=true) > :grails-test-examples-scaffolding:integrationTest

Test Runs Flakiness
UserControllerSpec > User list 2% 🟡

🏷️ Commit: b770e0b
▶️ Tests: 68956 executed
⚪️ Checks: 89/89 completed

Test Failures

UserControllerSpec > User list (:grails-test-examples-scaffolding:integrationTest in CI / Functional Tests (Java 21, indy=true))
geb.waiting.WaitTimeoutException: condition did not pass in 30 seconds (failed with exception)
	at geb.waiting.Wait.waitFor(Wait.groovy:128)
	at geb.waiting.DefaultWaitingSupport.doWaitFor(DefaultWaitingSupport.groovy:55)
	at geb.waiting.DefaultWaitingSupport.waitFor(DefaultWaitingSupport.groovy:41)
	at geb.Page.waitFor(Page.groovy:120)
	at com.example.pages.LoginPage.login(LoginPage.groovy:39)
	at com.example.UserControllerSpec.User list(UserControllerSpec.groovy:48)
Caused by: Assertion failed: 

title != pageTitle && $('input', name: 'username').empty
|     |  |         |
|     |  |         false
|     |  'Please sign in'
|     false
'Please sign in'

	at com.example.pages.LoginPage.login_closure1(LoginPage.groovy:39)
	at com.example.pages.LoginPage.login_closure1(LoginPage.groovy)
	at geb.waiting.Wait.waitFor(Wait.groovy:117)
	... 5 more

Rerun Controls

Select tests to mute in this pull request:

  • UserControllerSpec > User list

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app/docs.

@borinquenkid borinquenkid moved this from Todo to In Progress in Apache Grails Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Migrate configuration metadata to @ConfigurationProperties with annotation processor for Grails 8

4 participants