Repository navigation
fix: make config.docs Groovydoc aggregation work on Gradle 9 - #24
Conversation
aggregateGroovyApiDoc read the plugin project's Groovydoc classpath and Groovy
classpath, so the root task resolved another project's configurations. Gradle 9
rejects that in parallel builds ("Resolution of the configuration ... was
attempted without an exclusive lock"), which broke ./gradlew docs on Grails 8.
- Resolve the plugin project through a resolvable configuration of the root
project, with versions from the Grails BOM at the build's grailsVersion, and
infer the Groovy classpath from it
- Apply groovy-base so the Groovydoc task gets the conventions Gradle 9
requires (noIndex, noHelp, ...) and groovyRuntime
- Add PluginDocsSpec, building the aggregation in parallel on the current
Gradle and on the Grails 8 Gradle line
sbglasius
left a comment
There was a problem hiding this comment.
This an AI assisted review
Summary
The fix is correct. The root task now resolves only its own aggregateGroovydocClasspath. It no longer reaches into :plugin's Groovydoc classpath/groovyClasspath, so it stays within Gradle 9's per-project lock under --parallel. Applying groovy-base to get the new Groovydoc property conventions and groovyRuntime is also the right approach. I ran ./gradlew -p conventions test --tests '*PluginDocsSpec*' locally and both rows passed (Gradle 8.14.4 and 9.8.1).
Nothing blocking. A few suggestions:
1. The Gradle 9 test row still uses the Grails 7 BOM
PluginDocsSpec writes grailsVersion=${System.getProperty('grailsVersion')} (7.0.11) for both rows. The 9.8.1 row is therefore Gradle 9 + Grails 7 BOM/Groovy 4, which no real consumer runs. The grails8Version system property is already passed to the tests but isn't used here. Consider pairing each Gradle version with its Grails line ([gradle, grails, javaVersion]), with @Requires({ Runtime.version().feature() >= data.javaVersion }) as GrailsLineSpec does. That way the Groovy 5 / Grails 8 BOM path (the one that broke grails-export) is covered in CI and not only by the manual mavenLocal check.
2. dependsOn(upstream) is now redundant
Before this change, depending on :plugin:groovydoc came with reading its classpath. Now the only thing taken from the upstream task is source. Source files don't require the upstream Groovydoc to have run, and the plugin jar is already pulled in through the classpath build dependency. So every docs run also generates plugin/build/docs/groovydoc for nothing. The dependency can be dropped (keeping source = { upstream.get().source }).
3. Side effects of groovy-base on the root project
groovy-base brings in java-base → base. The root project now has clean/assemble/check/build tasks, a java extension and an empty sourceSets container. Root clean now deletes the root build/ dir, including build/docs. That's probably fine or even desirable, but it changes behaviour for consumers, so it may be worth a line in the release notes. It could also conflict with a consumer that registers its own clean in the root project.
4. Minor / style
- The
aggregateGroovydoc.configure { ... }block can go inside theconfigurations.dependencyScope('aggregateGroovydoc') { ... }closure. Then the declaration and its dependencies sit in one place. - Optional: the plugin's
runtimeElementsalready carries Groovy and its BOM constraints when it declaresimplementation platform(...). The explicit BOM +org.apache.groovy:groovy(and the hardgrailsVersionrequirement) may then be unnecessary. Keeping them is a safer bet for plugins that rely on the dependency-management plugin, so this is just a thought. - Not added by this PR, but nearby:
destinationDir = rootProject.layout.buildDirectory.dir('docs/gapi').get().asFileresolves eagerly.source = { upstream.get().source }still touches another project's task, which will matter if configuration cache or isolated projects is ever turned on.
…t groovydoc dependency - Pair each Gradle version in PluginDocsSpec with its Grails line (7.x on Gradle 8, 8.x on Gradle 9 with Java 21), like GrailsLineSpec - aggregateGroovyApiDoc no longer depends on the plugin project's groovydoc task: only its sources are used, and the plugin jar is built through the classpath
Problem
In consumers on Gradle 9 (e.g. grails-export on Grails 8 / Gradle 9.8.1),
./gradlew docsfails inaggregateGroovyApiDoc:The root task reads the plugin project's Groovydoc
classpathandgroovyClasspath, so it resolves another project's configurations. Gradle 9 rejects that in parallel builds (org.gradle.parallel=true), where a task only holds its own project's lock.There is a second Gradle 9 problem: a Groovydoc task without
groovy-basegets no default for the new required properties (noIndex,noHelp,noDeprecatedList, …), so validation fails.Fix (
config.docs.gradle)aggregateGroovydocdependency-scope configuration (Grails BOM at the build'sgrailsVersion,org.apache.groovy:groovy, plus a project dependency on the plugin project) and a resolvableaggregateGroovydocClasspath(runtime, library, jar). The Groovy classpath is inferred from it withgroovyRuntime. No other project's configuration is resolved.groovy-baseso the Groovydoc task gets its defaults andgroovyRuntimeis available.Tests
PluginDocsSpecrunsaggregateGroovyApiDocwith--parallelon the current Gradle (8.14.4) and on the Grails 8 Gradle line (9.8.1). With the old wiring it fails on 9.8.1 with the same exclusive-lock error. With the fix it passes on both../gradlew -p conventions testpasses on JDK 21: 9 tests, the Grails 8 line included../gradlew docsstill works on Grails 7 / Gradle 8.14.4../gradlew docs buildpasses on Grails 8.0.0.