Adopt AntBuilder groovydoc with javaVersion support#15420
Adopt AntBuilder groovydoc with javaVersion support#15420jamesfredley wants to merge 6 commits into7.0.xfrom
Conversation
Replace Gradle's built-in Groovydoc task execution with AntBuilder to support the javaVersion parameter introduced in Groovy 4.0.27 (GROOVY-11668). This is needed because Gradle's Groovydoc task does not expose javaVersion (gradle/gradle#33659 is not merged), causing Java 17+ source parsing failures. Changes across all groovydoc configurations: - gradle/docs-dependencies.gradle: central config for ~90 modules and both aggregate tasks (aggregateGroovydoc, aggregateDataMappingGroovydoc) - gradle/docs-config.gradle: per-module source directory setup - grails-doc/build.gradle: aggregate task source directories - grails-data-docs/stage/build.gradle: data mapping aggregate source dirs - grails-gradle/gradle/docs-config.gradle: independent AntBuilder setup - grails-data-hibernate5/docs/build.gradle: added groovy-ant dependency and AntBuilder execution - grails-data-mongodb/docs/build.gradle: added groovy-ant dependency and AntBuilder execution - grails-forge/gradle/doc-config.gradle: AntBuilder without javaVersion (forge uses Groovy 3.0.25 which predates the feature) Closes #15385 Assisted-by: Claude Code <[email protected]>
…ion plugin Move duplicated AntBuilder groovydoc execution, Matomo footer, documentation configuration registration, and task defaults into a shared convention plugin in build-logic. This eliminates ~490 lines of duplicated configuration across 8 build scripts while maintaining identical behavior. The plugin provides: - Documentation configuration registration with standard attributes - Common Groovydoc task defaults (author, timestamps, scripts) - AntBuilder-based execution with javaVersion support (Groovy 4.0.27+) - Matomo analytics footer - Source directory resolution from ext.groovydocSourceDirs or source sets - External documentation link support via ext.groovydocLinks - GrailsGroovydocExtension for per-project javaVersion control Build scripts retain project-specific configuration: dependencies, titles, source directories for aggregate tasks, and dynamic link resolution. Assisted-by: Claude Code <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR centralizes Groovydoc configuration and execution by introducing a build-logic convention plugin that runs Groovydoc via AntBuilder (to support Groovy’s javaVersion option), and updates multiple modules to use the new plugin while removing duplicated Groovydoc setup.
Changes:
- Added
org.apache.grails.buildsrc.groovydocconvention plugin (GrailsGroovydocPlugin+GrailsGroovydocExtension) to run Groovydoc throughorg.codehaus.groovy.ant.Groovydoc, with optionaljavaVersionsupport. - Updated shared/per-module docs Gradle scripts to apply the plugin and centralize common defaults (e.g., Matomo footer, shared dependency setup).
- Updated aggregate Groovydoc tasks to pass source directories via
ext.groovydocSourceDirsfor the new Ant-driven execution.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
build-logic/plugins/build.gradle |
Registers the new org.apache.grails.buildsrc.groovydoc convention plugin. |
build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsGroovydocPlugin.groovy |
Implements AntBuilder-based Groovydoc execution and shared defaults (footer, configuration). |
build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsGroovydocExtension.groovy |
Adds extension properties to control javaVersion and the javaVersion-passing toggle. |
gradle/docs-dependencies.gradle |
Applies the convention plugin and reworks Groovydoc link population via ext.groovydocLinks. |
grails-gradle/gradle/docs-config.gradle |
Applies the convention plugin and removes duplicated Groovydoc task configuration. |
grails-forge/gradle/doc-config.gradle |
Applies the convention plugin; disables javaVersion passing for Groovy 3.x and adds Ant Groovydoc deps. |
grails-doc/build.gradle |
Simplifies aggregate Groovydoc setup and provides ext.groovydocSourceDirs for Ant execution. |
grails-data-docs/stage/build.gradle |
Updates aggregate Groovydoc source handling and provides ext.groovydocSourceDirs. |
grails-data-hibernate5/docs/build.gradle |
Applies the convention plugin, adds required Ant Groovydoc deps, and supplies ext.groovydocSourceDirs. |
grails-data-mongodb/docs/build.gradle |
Applies the convention plugin, adds required Ant Groovydoc deps, and supplies ext.groovydocSourceDirs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Map<String, Object> antArgs = [ | ||
| destdir: destDir.absolutePath, | ||
| sourcepath: sourcepath, | ||
| packagenames: '**.*', | ||
| windowtitle: gdoc.windowTitle ?: '', |
There was a problem hiding this comment.
The Ant groovydoc call doesn't pass gdoc.classpath/gdoc.groovyClasspath (the documentation configuration is only used for taskdef). As a result, any Groovydoc classpath configured in build scripts is ignored during generation and can break type resolution. Pass the configured task classpath into the Ant task (e.g., classpath attribute or nested classpath).
There was a problem hiding this comment.
This is not correct. The Ant groovydoc task does not have a separate classpath attribute - the taskdef classloader (loaded with the documentation configuration) IS the type resolution classpath. Groovydoc parses sources syntactically rather than compiling them, so the taskdef classpath provides everything needed for type resolution.
The documentation configuration includes groovy-all, groovy-ant, groovy-templates, and any project-specific runtime dependencies. This is verified working - all 6,295+ HTML files generate correctly across all 7 groovydoc builds.
| List<File> sourceDirs = resolveSourceDirectories(gdoc, project) | ||
| if (sourceDirs.isEmpty()) { | ||
| project.logger.lifecycle("Skipping groovydoc for ${gdoc.name}: no source directories found") | ||
| return | ||
| } |
There was a problem hiding this comment.
Groovydoc source filtering from the Gradle task (e.g., gdoc.source, gdoc.includes/gdoc.excludes) isn’t honored here because the Ant task is driven only by sourcepath + packagenames. This makes existing exclusions in build scripts ineffective and can change which classes end up in the published API docs. Consider deriving the Ant inputs from gdoc.source (or translating excludes/includes into Ant filesets/excludepackagenames) so task configuration still applies.
There was a problem hiding this comment.
Acknowledged, though this is not a practical issue. The Ant groovydoc task uses sourcepath + packagenames by design - it does not support nested fileset elements.
The excludes configured on the Gradle task (META-INF/**, *.yml, *.properties, *.xml, Application.groovy, Bootstrap.groovy, resources.groovy) are either non-code files that groovydoc naturally ignores (it only processes .groovy and .java files) or convention files that do not exist in framework source directories. The generated output is identical to what the previous per-module AntBuilder configurations produced.
| def gebVersion = resolveProjectVersion('geb-spock') | ||
| if(gebVersion) { | ||
| gdoc.link("https://groovy.apache.org/geb/manual/${gebVersion}/api/", 'geb.') | ||
| if (gebVersion) { | ||
| links << [packages: 'geb.', href: "https://groovy.apache.org/geb/manual/${gebVersion}/api/"] | ||
| } |
There was a problem hiding this comment.
resolveProjectVersion() (defined above) never returns the resolved version when it is present, so gebVersion/testContainersVersion/etc will always be null here and no external groovydoc links will be added. Ensure resolveProjectVersion() returns the resolved version value.
There was a problem hiding this comment.
Good catch - this is correct. This was a pre-existing bug on 7.0.x (not introduced by this PR). The function had an early return null for the not-found case but was missing the implicit return of version as the last expression. In Groovy, the if statement evaluates to null when the condition is false and there is no else, so the function always returned null.
Fixed in bf36531 by adding version as the last expression in the function. External groovydoc links (geb, testcontainers, spring, spring-boot) should now be included correctly.
| import org.gradle.api.tasks.javadoc.Groovydoc | ||
|
|
||
| @CompileStatic | ||
| class GrailsGroovydocPlugin implements Plugin<Project> { |
There was a problem hiding this comment.
What are your thoughts about keeping this plugin generic? i.e. do not put specific grails-core configuration in it and instead configure it like we configured groovydoc before? That way if gradle merges the upstream change, we don't have to separate out all of the configuration.
There was a problem hiding this comment.
Addressed in bf36531. The plugin is now split into two layers:
-
GroovydocEnhancerPlugin(org.apache.grails.buildsrc.groovydoc-enhancer) - Generic base plugin with all the AntBuilder logic, source resolution, link support, anddocumentationconfiguration. No Grails-specific code. Publishable and reusable by anyone. -
GrailsGroovydocPlugin(org.apache.grails.buildsrc.groovydoc) - Thin layer that applies the base plugin and setsfooterto the Matomo analytics snippet. This is the only Grails-specific piece.
The base extension also includes a useAntBuilder flag (default true). When Gradle merges their javaVersion support (gradle/gradle#33659), setting groovydocEnhancer { useAntBuilder = false } will skip the AntBuilder action replacement and let Gradle's built-in task run - no code changes needed to switch back.
Separate GrailsGroovydocPlugin into a generic GroovydocEnhancerPlugin (with GroovydocEnhancerExtension) and a thin GrailsGroovydocPlugin that applies the base plugin and sets the Matomo footer. This makes the core AntBuilder groovydoc logic publishable and reusable by anyone, while keeping Grails-specific customizations in their own layer. The base plugin supports a useAntBuilder flag so projects can easily switch back to Gradle's built-in Groovydoc task if Gradle merges their javaVersion support (gradle/gradle#33659). Also fix a pre-existing bug in resolveProjectVersion() in docs-dependencies.gradle where the function never returned the resolved version string, causing all external groovydoc links (geb, testcontainers, spring, spring-boot) to be silently omitted. Assisted-by: Claude Code <[email protected]>
jdaugherty
left a comment
There was a problem hiding this comment.
Have you checked the build scans to ensure no new warnings have appeared with adding this?
| @Inject | ||
| GroovydocEnhancerExtension(ObjectFactory objects, Project project) { | ||
| javaVersion = objects.property(String).convention( | ||
| "JAVA_${GradleUtils.findProperty(project, 'javaVersion') ?: '17'}" as String |
There was a problem hiding this comment.
javaVersion should always be set or it should error?
There was a problem hiding this comment.
I believe it will be null, unless you set it in the end project and we are defaulting to 17 here given that is the base for Grails 7.
There was a problem hiding this comment.
Use a provider and it will be set correctly. project.provider { /* your existing JAVA_ code */ }
There was a problem hiding this comment.
Done in 3702656 - wrapped the convention in project.provider { ... } so javaVersion resolves lazily at execution time.
|
@jdaugherty Deprecations look the same Before: https://develocity.apache.org/s/ocm5dcxmesrps/deprecations (this is run later, but was off 7.0.x) |
Wrap the javaVersion convention in project.provider so the property is resolved at execution time rather than configuration time. Assisted-by: Claude Code <[email protected]>
| GroovydocEnhancerExtension(ObjectFactory objects, Project project) { | ||
| javaVersion = objects.property(String).convention( | ||
| "JAVA_${GradleUtils.findProperty(project, 'javaVersion') ?: '17'}" as String | ||
| project.provider { "JAVA_${GradleUtils.findProperty(project, 'javaVersion') ?: '17'}" as String } |
There was a problem hiding this comment.
You shouldn't need the default of 17 now.
There was a problem hiding this comment.
The end grails project will not have javaVersion set in the project, unless they are attempting to overriode and change the version lower or higher for groovydoc support. 17 is here as the automatic default.
There was a problem hiding this comment.
The end grails app? This is for our applications and we define javaVersion in the grade.properties - which is copied to every project in this build. This comment doesn't make sense to me. If it's not copied, we have a bug that's why we should leave it with no default.
There was a problem hiding this comment.
grails-core does, but if we were to publish this and another project used it ... was what I was thinking about. IE the end Grails App.
There was a problem hiding this comment.
There are so many other changes that have to be made to publish this. Lets do whats best for grails core and address that when/if we decide to publish?
|
|
||
| gdoc.actions.clear() | ||
| gdoc.doLast { | ||
| def destDir = gdoc.destinationDir.tap { it.mkdirs() } |
There was a problem hiding this comment.
Small suggestion here, since destinationDir is deprecated in Gradle 8 and getting removed in Gradle 9, should we maybe use destinationDirectory.get().asFile instead? Just thought it might help avoid some deprecation warnings down the line! Let me know if that makes sense.
| def docConfig = project.configurations.findByName('documentation') | ||
| if (!docConfig) { | ||
| project.logger.warn( | ||
| 'Skipping groovydoc for {}: \'documentation\' configuration not found', | ||
| gdoc.name | ||
| ) | ||
| return | ||
| } | ||
|
|
||
| project.ant.taskdef( | ||
| name: 'groovydoc', | ||
| classname: 'org.codehaus.groovy.ant.Groovydoc', | ||
| classpath: docConfig.asPath | ||
| ) |
There was a problem hiding this comment.
Just noticed this while reading through, could we use gdoc.groovyClasspath here instead of looking up the configuration manually?
Since a task's groovyClasspath is already tracked as an input by Gradle, using it directly might be a little safer for the build cache.
Maybe something like this?
def classpath = gdoc.groovyClasspath
if (!classpath || classpath.empty) {
project.logger.warn('Skipping groovydoc for {}: groovyClasspath is empty', gdoc.name)
return
}
project.ant.taskdef(
name: 'groovydoc',
classname: 'org.codehaus.groovy.ant.Groovydoc',
classpath: classpath.asPath
)
Summary
Replaces Gradle built-in Groovydoc task execution with direct AntBuilder invocation of the Groovy
org.codehaus.groovy.ant.GroovydocAnt task, enabling thejavaVersionparameter introduced in Groovy 4.0.27 (GROOVY-11668). The AntBuilder logic is centralized into a layered plugin architecture inbuild-logic/:GroovydocEnhancerPlugin- Generic, publishable base plugin with all AntBuilder logicGrailsGroovydocPlugin- Thin Grails-specific layer that applies the base and sets the Matomo footerAlso fixes a pre-existing bug in
resolveProjectVersion()where external groovydoc links (geb, testcontainers, spring, spring-boot) were silently omitted.Problem
Gradle
Groovydoctask does not expose thejavaVersionproperty (gradle/gradle#33659 is not merged). Without it, groovydoc defaults toJAVA_11language level when parsing Java source files, causing failures when processing Java 17+ features like sealed classes, records, and pattern matching.Solution
1. AntBuilder groovydoc with javaVersion
For each Groovydoc task:
@TaskActionexecutiondoLastthat uses AntBuilder to callorg.codehaus.groovy.ant.GroovydocdirectlyjavaVersion: JAVA_17(derived fromgradle.properties) to enable correct Java 17+ source parsing2. Layered plugin architecture
Base plugin:
GroovydocEnhancerPlugin(org.apache.grails.buildsrc.groovydoc-enhancer)Generic, publishable plugin with no Grails-specific logic:
javaVersionsupportdocumentationconfiguration for Ant classpathext.groovydocSourceDirsext.groovydocLinksGroovydocAccessenum viaClass.forName(not on build-logic compile classpath)Property<T>/ plain-value API viaresolveGroovydocProperty()GroovydocEnhancerExtensionprovides:javaVersion(default:JAVA_17) - target Java version for Groovydoc outputjavaVersionEnabled(default:true) - allows disabling for Groovy 3.x (Forge uses 3.0.25)useAntBuilder(default:true) - switch back to Gradle's built-in task when Support javaVersion property for groovydoc gradle/gradle#33659 mergesfooter- customizable HTML footer for generated docsGrails plugin:
GrailsGroovydocPlugin(org.apache.grails.buildsrc.groovydoc)Thin Grails-specific layer:
GroovydocEnhancerPluginfooterto the Matomo analytics snippetSwitch-back path
When Gradle merges their
javaVersionPR, setgroovydocEnhancer { useAntBuilder = false }to skip the AntBuilder action replacement and use Gradle's built-in task instead.3. Bug fix: resolveProjectVersion()
Fixed a pre-existing bug where
resolveProjectVersion()indocs-dependencies.gradlenever returned the resolved version string. The function had an early return for null but was missing the return of the actual version value, causing all external groovydoc links to be silently omitted.Files changed
build-logic/plugins/build.gradlegroovydocEnhancerandgrailsGroovydocplugin IDsbuild-logic/.../GroovydocEnhancerPlugin.groovybuild-logic/.../GroovydocEnhancerExtension.groovyjavaVersion,javaVersionEnabled,useAntBuilder,footerbuild-logic/.../GrailsGroovydocPlugin.groovygradle/docs-dependencies.gradlegradle/docs-config.gradleincludeInApiDocsgrails-gradle/gradle/docs-config.gradlegrails-forge/gradle/doc-config.gradlejavaVersionEnabled = false) + depsgrails-data-hibernate5/docs/build.gradlegrails-data-mongodb/docs/build.gradlegrails-doc/build.gradlegrails-data-docs/stage/build.gradleTechnical details
resolveGroovydocProperty()helper handles the mix of plain values andProperty<T>wrappers in GradleGroovydoctask APIext.groovydocSourceDirsfor aggregate tasks, or derived from source sets for per-module tasksdocumentationconfiguration already includesgroovy-antin the central config; hibernate5 and mongodb docs needed it addedenabledbefore running any actionsdoFirstto populateext.groovydocLinks, plugin reads them indoLastTesting
:grails-core:groovydoc,:grails-bootstrap:groovydoc:grails-gradle-plugins:groovydoc:grails-doc:aggregateGroovydoc(3,929 HTML files generated):grails-data-docs-stage:aggregateDataMappingGroovydoc./gradlew codeStylepassesjavaVersionEnabled = false)Closes #15385