This is an automated email from the ASF dual-hosted git repository.

jamesfredley pushed a commit to branch refactor/centralize-groovydoc-plugin
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 3c50821ea7aa98b15561e8e6672a45c91411e8f9
Author: James Fredley <[email protected]>
AuthorDate: Thu Feb 19 20:32:21 2026 -0500

    Centralize groovydoc configuration into GrailsGroovydocPlugin convention 
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]>
---
 build-logic/plugins/build.gradle                   |   4 +
 .../buildsrc/GrailsGroovydocExtension.groovy       |  67 ++++++++
 .../grails/buildsrc/GrailsGroovydocPlugin.groovy   | 186 +++++++++++++++++++++
 gradle/docs-config.gradle                          |   9 -
 gradle/docs-dependencies.gradle                    | 121 +-------------
 grails-data-docs/stage/build.gradle                |  17 --
 grails-data-hibernate5/docs/build.gradle           |  81 +--------
 grails-data-mongodb/docs/build.gradle              |  82 +--------
 grails-doc/build.gradle                            |  17 --
 grails-forge/gradle/doc-config.gradle              |  93 +----------
 grails-gradle/gradle/docs-config.gradle            |  89 +---------
 11 files changed, 275 insertions(+), 491 deletions(-)

diff --git a/build-logic/plugins/build.gradle b/build-logic/plugins/build.gradle
index e85254e152..f750808908 100644
--- a/build-logic/plugins/build.gradle
+++ b/build-logic/plugins/build.gradle
@@ -62,6 +62,10 @@ gradlePlugin {
             id = 'org.apache.grails.gradle.grails-code-style'
             implementationClass = 
'org.apache.grails.buildsrc.GrailsCodeStylePlugin'
         }
+        register('grailsGroovydoc') {
+            id = 'org.apache.grails.buildsrc.groovydoc'
+            implementationClass = 
'org.apache.grails.buildsrc.GrailsGroovydocPlugin'
+        }
         register('grailsRepoSettings') {
             id = 'org.apache.grails.buildsrc.repo'
             implementationClass = 
'org.apache.grails.buildsrc.GrailsRepoSettingsPlugin'
diff --git 
a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsGroovydocExtension.groovy
 
b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsGroovydocExtension.groovy
new file mode 100644
index 0000000000..98cefd434c
--- /dev/null
+++ 
b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsGroovydocExtension.groovy
@@ -0,0 +1,67 @@
+/*
+ *  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.
+ */
+package org.apache.grails.buildsrc
+
+import javax.inject.Inject
+
+import groovy.transform.CompileStatic
+
+import org.gradle.api.Project
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.Property
+
+/**
+ * Extension for configuring the Grails Groovydoc convention plugin.
+ *
+ * <p>Allows per-project control over the {@code javaVersion} parameter
+ * passed to the Groovy Ant groovydoc task. The {@code javaVersion}
+ * parameter was added in Groovy 4.0.27 (GROOVY-11668) and controls
+ * the JavaParser language level used when parsing Java sources.</p>
+ *
+ * @since 7.0.8
+ */
+@CompileStatic
+class GrailsGroovydocExtension {
+
+    /**
+     * The Java language level string passed to the groovydoc Ant task's
+     * {@code javaVersion} parameter (e.g. {@code "JAVA_17"}, {@code 
"JAVA_21"}).
+     *
+     * <p>Defaults to {@code "JAVA_${javaVersion}"} where {@code javaVersion}
+     * is read from the project property, falling back to {@code 
"JAVA_17"}.</p>
+     */
+    final Property<String> javaVersion
+
+    /**
+     * Whether to pass the {@code javaVersion} parameter to the groovydoc
+     * Ant task. Set to {@code false} for projects using Groovy versions
+     * older than 4.0.27 (which do not support the parameter).
+     *
+     * <p>Defaults to {@code true}.</p>
+     */
+    final Property<Boolean> javaVersionEnabled
+
+    @Inject
+    GrailsGroovydocExtension(ObjectFactory objects, Project project) {
+        javaVersion = objects.property(String).convention(
+                "JAVA_${GradleUtils.findProperty(project, 'javaVersion') ?: 
'17'}" as String
+        )
+        javaVersionEnabled = objects.property(Boolean).convention(true)
+    }
+}
diff --git 
a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsGroovydocPlugin.groovy
 
b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsGroovydocPlugin.groovy
new file mode 100644
index 0000000000..d5e5875af2
--- /dev/null
+++ 
b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsGroovydocPlugin.groovy
@@ -0,0 +1,186 @@
+/*
+ *  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.
+ */
+package org.apache.grails.buildsrc
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.attributes.Bundling
+import org.gradle.api.attributes.Category
+import org.gradle.api.attributes.Usage
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.SourceSetContainer
+import org.gradle.api.tasks.javadoc.Groovydoc
+
+@CompileStatic
+class GrailsGroovydocPlugin implements Plugin<Project> {
+
+    static final String MATOMO_FOOTER = '''\
+<!-- Matomo -->
+<script>
+    var _paq = window._paq = window._paq || [];
+    /* tracker methods like "setCustomDimension" should be called before 
"trackPageView" */
+    _paq.push(["setDoNotTrack", true]);
+    _paq.push(["disableCookies"]);
+    _paq.push(['trackPageView']);
+    _paq.push(['enableLinkTracking']);
+    (function() {
+        var u="https://analytics.apache.org/";;
+        _paq.push(['setTrackerUrl', u+'matomo.php']);
+        _paq.push(['setSiteId', '79']);
+        var d=document, g=d.createElement('script'), 
s=d.getElementsByTagName('script')[0];
+        g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
+    })();
+</script>
+<!-- End Matomo Code -->'''
+
+    @Override
+    void apply(Project project) {
+        GrailsGroovydocExtension extension = project.extensions.create(
+                'grailsGroovydoc', GrailsGroovydocExtension, project
+        )
+        registerDocumentationConfiguration(project)
+        configureGroovydocDefaults(project)
+        configureAntBuilderExecution(project, extension)
+    }
+
+    private static void registerDocumentationConfiguration(Project project) {
+        if (project.configurations.names.contains('documentation')) {
+            return
+        }
+        project.configurations.register('documentation') { Configuration 
config ->
+            config.canBeConsumed = false
+            config.canBeResolved = true
+            config.attributes { container ->
+                container.attribute(Category.CATEGORY_ATTRIBUTE, 
project.objects.named(Category, Category.LIBRARY))
+                container.attribute(Bundling.BUNDLING_ATTRIBUTE, 
project.objects.named(Bundling, Bundling.EXTERNAL))
+                container.attribute(Usage.USAGE_ATTRIBUTE, 
project.objects.named(Usage, Usage.JAVA_RUNTIME))
+            }
+        }
+    }
+
+    @CompileDynamic
+    private static void configureGroovydocDefaults(Project project) {
+        project.tasks.withType(Groovydoc).configureEach { Groovydoc gdoc ->
+            gdoc.includeAuthor = false
+            gdoc.includeMainForScripts = false
+            gdoc.processScripts = false
+            gdoc.noTimestamp = true
+            gdoc.noVersionStamp = false
+            gdoc.footer = MATOMO_FOOTER
+            if (project.configurations.names.contains('documentation')) {
+                gdoc.groovyClasspath = 
project.configurations.getByName('documentation')
+            }
+        }
+    }
+
+    @CompileDynamic
+    private static void configureAntBuilderExecution(Project project, 
GrailsGroovydocExtension extension) {
+        project.tasks.withType(Groovydoc).configureEach { Groovydoc gdoc ->
+            gdoc.actions.clear()
+            gdoc.doLast {
+                File destDir = gdoc.destinationDir
+                destDir.mkdirs()
+
+                List<File> sourceDirs = resolveSourceDirectories(gdoc, project)
+                if (sourceDirs.isEmpty()) {
+                    project.logger.lifecycle("Skipping groovydoc for 
${gdoc.name}: no source directories found")
+                    return
+                }
+
+                Configuration docConfig = 
project.configurations.findByName('documentation')
+                if (!docConfig) {
+                    project.logger.warn("Skipping groovydoc for ${gdoc.name}: 
'documentation' configuration not found")
+                    return
+                }
+
+                project.ant.taskdef(
+                        name: 'groovydoc',
+                        classname: 'org.codehaus.groovy.ant.Groovydoc',
+                        classpath: docConfig.asPath
+                )
+
+                List<Map<String, String>> links = resolveLinks(gdoc)
+                String sourcepath = sourceDirs.collect { it.absolutePath 
}.join(File.pathSeparator)
+
+                Map<String, Object> antArgs = [
+                        destdir: destDir.absolutePath,
+                        sourcepath: sourcepath,
+                        packagenames: '**.*',
+                        windowtitle: gdoc.windowTitle ?: '',
+                        doctitle: gdoc.docTitle ?: '',
+                        footer: gdoc.footer ?: '',
+                        access: 
resolveGroovydocProperty(gdoc.access)?.name()?.toLowerCase() ?: 'protected',
+                        author: resolveGroovydocProperty(gdoc.includeAuthor) 
as String,
+                        noTimestamp: 
resolveGroovydocProperty(gdoc.noTimestamp) as String,
+                        noVersionStamp: 
resolveGroovydocProperty(gdoc.noVersionStamp) as String,
+                        processScripts: 
resolveGroovydocProperty(gdoc.processScripts) as String,
+                        includeMainForScripts: 
resolveGroovydocProperty(gdoc.includeMainForScripts) as String
+                ]
+
+                if (extension.javaVersionEnabled.get()) {
+                    antArgs.put('javaVersion', extension.javaVersion.get())
+                }
+
+                project.ant.groovydoc(antArgs) {
+                    for (Map<String, String> l in links) {
+                        link(packages: l.packages, href: l.href)
+                    }
+                }
+            }
+        }
+    }
+
+    @CompileDynamic
+    private static List<File> resolveSourceDirectories(Groovydoc gdoc, Project 
project) {
+        if (gdoc.ext.has('groovydocSourceDirs') && 
gdoc.ext.groovydocSourceDirs) {
+            return (gdoc.ext.groovydocSourceDirs as List<File>).findAll { 
it.exists() }.unique() as List<File>
+        }
+
+        List<File> sourceDirs = []
+        SourceSetContainer sourceSets = 
project.extensions.findByType(SourceSetContainer)
+        if (sourceSets) {
+            SourceSet mainSS = sourceSets.findByName('main')
+            if (mainSS) {
+                sourceDirs.addAll(mainSS.groovy.srcDirs.findAll { it.exists() 
})
+                sourceDirs.addAll(mainSS.java.srcDirs.findAll { it.exists() })
+            }
+        }
+        sourceDirs.unique() as List<File>
+    }
+
+    @CompileDynamic
+    private static List<Map<String, String>> resolveLinks(Groovydoc gdoc) {
+        if (gdoc.ext.has('groovydocLinks')) {
+            return gdoc.ext.groovydocLinks as List<Map<String, String>>
+        }
+        []
+    }
+
+    static Object resolveGroovydocProperty(Object value) {
+        if (value instanceof Provider) {
+            return ((Provider) value).getOrNull()
+        }
+        value
+    }
+}
diff --git a/gradle/docs-config.gradle b/gradle/docs-config.gradle
index 76a14b619b..5b5c7d1794 100644
--- a/gradle/docs-config.gradle
+++ b/gradle/docs-config.gradle
@@ -21,13 +21,4 @@ apply from: 
rootProject.layout.projectDirectory.file('gradle/docs-dependencies.g
 
 ext {
     includeInApiDocs = true
-}
-
-// Set source directories for AntBuilder groovydoc execution (per-module tasks)
-tasks.withType(Groovydoc).configureEach { Groovydoc gdoc ->
-    SourceSetContainer sourceSets = 
project.extensions.getByType(SourceSetContainer)
-    SourceSet mainSS = sourceSets.findByName('main')
-    if (mainSS) {
-        gdoc.ext.groovydocSourceDirs = (mainSS.groovy.srcDirs + 
mainSS.java.srcDirs).findAll { it.exists() }
-    }
 }
\ No newline at end of file
diff --git a/gradle/docs-dependencies.gradle b/gradle/docs-dependencies.gradle
index 23b75693b9..afb56aec4a 100644
--- a/gradle/docs-dependencies.gradle
+++ b/gradle/docs-dependencies.gradle
@@ -16,15 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-configurations.register('documentation') {
-    canBeConsumed = false
-    canBeResolved = true
-    attributes {
-        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, 
Category.LIBRARY))
-        attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, 
Bundling.EXTERNAL))
-        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 
Usage.JAVA_RUNTIME))
-    }
-}
+apply plugin: 'org.apache.grails.buildsrc.groovydoc'
 
 dependencies {
     add('documentation', platform(project(':grails-bom')))
@@ -50,127 +42,32 @@ String resolveProjectVersion(String artifact) {
     }
 }
 
-// Resolve a Groovydoc task property that may be either a Gradle Property<T> 
or a plain value.
-// Gradle 8.x Groovydoc uses a mix of both types depending on the property.
-Object resolveGroovydocProperty(Object value) {
-    if (value instanceof org.gradle.api.provider.Provider) {
-        return value.getOrNull()
-    }
-    return value
-}
-
-// Compute the javaVersion string for groovydoc's JavaParser language level.
-// Requires Groovy 4.0.27+ (GROOVY-11668). Gradle's built-in Groovydoc task 
does not
-// expose this parameter (https://github.com/gradle/gradle/issues/33659), so 
we replace
-// the task execution with AntBuilder to pass it directly to the Groovy Ant 
task.
-String groovydocJavaVersion = "JAVA_${project.findProperty('javaVersion') ?: 
'17'}"
-
 tasks.withType(Groovydoc).configureEach { Groovydoc gdoc ->
     gdoc.exclude('META-INF/**', '*yml', '*properties', '*xml', 
'**/Application.groovy', '**/Bootstrap.groovy', '**/resources.groovy')
-    gdoc.groovyClasspath = configurations.documentation
     gdoc.windowTitle = "${project.findProperty('pomArtifactId') ?: 
project.name} - $projectVersion"
     gdoc.docTitle = "${project.findProperty('pomArtifactId') ?: project.name} 
- $projectVersion"
-    gdoc.access = GroovydocAccess.PROTECTED
-    gdoc.includeAuthor = false
-    gdoc.includeMainForScripts = false
-    gdoc.processScripts = false
-    gdoc.noTimestamp = true
-    gdoc.noVersionStamp = false
-    gdoc.footer = '''<!-- Matomo -->
-<script>
-    var _paq = window._paq = window._paq || [];
-    /* tracker methods like "setCustomDimension" should be called before 
"trackPageView" */
-    _paq.push(["setDoNotTrack", true]);
-    _paq.push(["disableCookies"]);
-    _paq.push(['trackPageView']);
-    _paq.push(['enableLinkTracking']);
-    (function() {
-        var u="https://analytics.apache.org/";;
-        _paq.push(['setTrackerUrl', u+'matomo.php']);
-        _paq.push(['setSiteId', '79']);
-        var d=document, g=d.createElement('script'), 
s=d.getElementsByTagName('script')[0];
-        g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
-    })();
-</script>
-<!-- End Matomo Code -->'''
 
-    // Replace Gradle's built-in Groovydoc execution with AntBuilder to 
support javaVersion.
-    // See: https://github.com/apache/grails-core/issues/15385
-    gdoc.actions.clear()
-    gdoc.doLast {
-        def destDir = gdoc.destinationDir
-        destDir.mkdirs()
-
-        // Source directories: set by modules via ext.groovydocSourceDirs, or 
derived from source sets
-        List<File> sourceDirs
-        if (gdoc.ext.has('groovydocSourceDirs') && 
gdoc.ext.groovydocSourceDirs) {
-            sourceDirs = (gdoc.ext.groovydocSourceDirs as List<File>).findAll 
{ it.exists() }
-        } else {
-            sourceDirs = []
-            def sourceSetsExt = 
project.extensions.findByType(SourceSetContainer)
-            if (sourceSetsExt) {
-                def mainSS = sourceSetsExt.findByName('main')
-                if (mainSS) {
-                    sourceDirs.addAll(mainSS.groovy.srcDirs.findAll { 
it.exists() })
-                    sourceDirs.addAll(mainSS.java.srcDirs.findAll { 
it.exists() })
-                }
-            }
-        }
-        sourceDirs = sourceDirs.unique()
-
-        if (sourceDirs.isEmpty()) {
-            logger.lifecycle("Skipping groovydoc for ${gdoc.name}: no source 
directories found")
-            return
-        }
-
-        ant.taskdef(
-                name: 'groovydoc',
-                classname: 'org.codehaus.groovy.ant.Groovydoc',
-                classpath: configurations.documentation.asPath
-        )
-
-        // Resolve dynamic links from project dependencies
-        List<Map<String, String>> groovydocLinks = []
+    gdoc.doFirst {
+        List<Map<String, String>> links = []
         def gebVersion = resolveProjectVersion('geb-spock')
         if (gebVersion) {
-            groovydocLinks << [packages: 'geb.', href: 
"https://groovy.apache.org/geb/manual/${gebVersion}/api/";]
+            links << [packages: 'geb.', href: 
"https://groovy.apache.org/geb/manual/${gebVersion}/api/";]
         }
         def testContainersVersion = resolveProjectVersion('testcontainers')
         if (testContainersVersion) {
-            groovydocLinks << [packages: 'org.testcontainers.', href: 
"https://javadoc.io/doc/org.testcontainers/testcontainers/${testContainersVersion}/";]
+            links << [packages: 'org.testcontainers.', href: 
"https://javadoc.io/doc/org.testcontainers/testcontainers/${testContainersVersion}/";]
         }
         def springVersion = resolveProjectVersion('spring-core')
         if (springVersion) {
-            groovydocLinks << [packages: 'org.springframework.core.', href: 
"https://docs.spring.io/spring-framework/docs/${springVersion}/javadoc-api/";]
+            links << [packages: 'org.springframework.core.', href: 
"https://docs.spring.io/spring-framework/docs/${springVersion}/javadoc-api/";]
         }
         def springBootVersion = resolveProjectVersion('spring-boot')
         if (springBootVersion) {
-            groovydocLinks << [packages: 'org.springframework.boot.', href: 
"https://docs.spring.io/spring-boot/docs/${springBootVersion}/api/";]
+            links << [packages: 'org.springframework.boot.', href: 
"https://docs.spring.io/spring-boot/docs/${springBootVersion}/api/";]
         }
         if (gdoc.ext.has('groovydocLinks')) {
-            groovydocLinks.addAll(gdoc.ext.groovydocLinks as List<Map<String, 
String>>)
-        }
-
-        def sourcepath = sourceDirs.collect { it.absolutePath 
}.join(File.pathSeparator)
-
-        ant.groovydoc(
-                destdir: destDir.absolutePath,
-                sourcepath: sourcepath,
-                packagenames: '**.*',
-                windowtitle: gdoc.windowTitle ?: '',
-                doctitle: gdoc.docTitle ?: '',
-                footer: gdoc.footer ?: '',
-                access: 
resolveGroovydocProperty(gdoc.access)?.name()?.toLowerCase() ?: 'protected',
-                author: resolveGroovydocProperty(gdoc.includeAuthor) as String,
-                noTimestamp: resolveGroovydocProperty(gdoc.noTimestamp) as 
String,
-                noVersionStamp: resolveGroovydocProperty(gdoc.noVersionStamp) 
as String,
-                processScripts: resolveGroovydocProperty(gdoc.processScripts) 
as String,
-                includeMainForScripts: 
resolveGroovydocProperty(gdoc.includeMainForScripts) as String,
-                javaVersion: groovydocJavaVersion
-        ) {
-            for (Map<String, String> l in groovydocLinks) {
-                link(packages: l.packages, href: l.href)
-            }
+            links.addAll(gdoc.ext.groovydocLinks as List<Map<String, String>>)
         }
+        gdoc.ext.groovydocLinks = links
     }
 }
\ No newline at end of file
diff --git a/grails-data-docs/stage/build.gradle 
b/grails-data-docs/stage/build.gradle
index 4d13bb6b01..5af7d88544 100644
--- a/grails-data-docs/stage/build.gradle
+++ b/grails-data-docs/stage/build.gradle
@@ -25,23 +25,6 @@ apply from: 
rootProject.layout.projectDirectory.file('gradle/docs-dependencies.g
 combinedGroovydoc.configure { Groovydoc task ->
     task.windowTitle = "Grails Data Mapping API - ${projectVersion}"
     task.docTitle = "Grails Data Mapping API - ${projectVersion}"
-    task.footer = '''<!-- Matomo -->
-<script>
-    var _paq = window._paq = window._paq || [];
-    /* tracker methods like "setCustomDimension" should be called before 
"trackPageView" */
-    _paq.push(["setDoNotTrack", true]);
-    _paq.push(["disableCookies"]);
-    _paq.push(['trackPageView']);
-    _paq.push(['enableLinkTracking']);
-    (function() {
-        var u="https://analytics.apache.org/";;
-        _paq.push(['setTrackerUrl', u+'matomo.php']);
-        _paq.push(['setSiteId', '79']);
-        var d=document, g=d.createElement('script'), 
s=d.getElementsByTagName('script')[0];
-        g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
-    })();
-</script>
-<!-- End Matomo Code -->'''
 
     Set<Project> docProjects = rootProject.subprojects.findAll {
         it.name in [
diff --git a/grails-data-hibernate5/docs/build.gradle 
b/grails-data-hibernate5/docs/build.gradle
index 565da447cb..be9c04f90d 100644
--- a/grails-data-hibernate5/docs/build.gradle
+++ b/grails-data-hibernate5/docs/build.gradle
@@ -31,13 +31,7 @@ ext {
     coreProjects = ['grails-datastore-core', 'grails-datamapping-core']
 }
 
-configurations {
-    documentation {
-        attributes {
-            attribute(Bundling.BUNDLING_ATTRIBUTE, (Bundling) 
(objects.named(Bundling, 'external')))
-        }
-    }
-}
+apply plugin: 'org.apache.grails.buildsrc.groovydoc'
 
 dependencies {
     documentation platform(project(':grails-bom'))
@@ -94,41 +88,12 @@ tasks.named('asciidoctor', AsciidoctorTask) { 
AsciidoctorTask it ->
     ]
 }
 
-// Resolve a Groovydoc task property that may be either a Gradle Property<T> 
or a plain value.
-Object resolveGroovydocProperty(Object value) {
-    if (value instanceof org.gradle.api.provider.Provider) {
-        return value.getOrNull()
-    }
-    return value
-}
-
-// Compute the javaVersion string for groovydoc's JavaParser language level.
-// Requires Groovy 4.0.27+ (GROOVY-11668).
-String groovydocJavaVersion = "JAVA_${project.findProperty('javaVersion') ?: 
'17'}"
-
 tasks.withType(Groovydoc).configureEach {
     it.dependsOn(rootProject.subprojects
             .findAll { it.findProperty('gormApiDocs') }
             .collect { ":${it.name}:groovydoc" })
 
     it.docTitle = "GORM for Hibernate 5 - $project.version"
-    it.footer = '''<!-- Matomo -->
-<script>
-    var _paq = window._paq = window._paq || [];
-    /* tracker methods like "setCustomDimension" should be called before 
"trackPageView" */
-    _paq.push(["setDoNotTrack", true]);
-    _paq.push(["disableCookies"]);
-    _paq.push(['trackPageView']);
-    _paq.push(['enableLinkTracking']);
-    (function() {
-        var u="https://analytics.apache.org/";;
-        _paq.push(['setTrackerUrl', u+'matomo.php']);
-        _paq.push(['setSiteId', '79']);
-        var d=document, g=d.createElement('script'), 
s=d.getElementsByTagName('script')[0];
-        g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
-    })();
-</script>
-<!-- End Matomo Code -->'''
 
     def sourceFiles = coreProjects.collect {
         rootProject.layout.projectDirectory.files("$it/src/main/groovy")
@@ -140,15 +105,8 @@ tasks.withType(Groovydoc).configureEach {
 
     it.source = sourceFiles
     it.destinationDir = 
layout.buildDirectory.dir('combined-api/api').get().asFile
-    it.access = GroovydocAccess.PROTECTED
-    it.processScripts = false
-    it.includeMainForScripts = false
-    it.includeAuthor = false
     it.classpath = configurations.documentation
-    it.groovyClasspath += configurations.documentation
-    it.noVersionStamp = false
 
-    // Collect source directories for AntBuilder groovydoc
     List<File> groovydocSrcDirs = coreProjects.collect {
         rootProject.layout.projectDirectory.dir("$it/src/main/groovy").asFile
     }
@@ -156,43 +114,6 @@ tasks.withType(Groovydoc).configureEach {
             .findAll { sp -> sp.findProperty('gormApiDocs') }
             .each { sp -> groovydocSrcDirs << new File(sp.projectDir, 
'src/main/groovy') }
     it.ext.groovydocSourceDirs = groovydocSrcDirs
-
-    // Replace Gradle's built-in Groovydoc execution with AntBuilder to 
support javaVersion.
-    // See: https://github.com/apache/grails-core/issues/15385
-    it.actions.clear()
-    it.doLast { Groovydoc gdoc ->
-        def destDir = gdoc.destinationDir
-        destDir.mkdirs()
-
-        List<File> sourceDirs = (gdoc.ext.groovydocSourceDirs as 
List<File>).findAll { File f -> f.exists() }.unique()
-        if (sourceDirs.isEmpty()) {
-            logger.lifecycle("Skipping groovydoc for ${gdoc.name}: no source 
directories found")
-            return
-        }
-
-        ant.taskdef(
-                name: 'groovydoc',
-                classname: 'org.codehaus.groovy.ant.Groovydoc',
-                classpath: configurations.documentation.asPath
-        )
-
-        def sourcepath = sourceDirs.collect { File f -> f.absolutePath 
}.join(File.pathSeparator)
-
-        ant.groovydoc(
-                destdir: destDir.absolutePath,
-                sourcepath: sourcepath,
-                packagenames: '**.*',
-                doctitle: gdoc.docTitle ?: '',
-                footer: gdoc.footer ?: '',
-                access: 
resolveGroovydocProperty(gdoc.access)?.name()?.toLowerCase() ?: 'protected',
-                author: resolveGroovydocProperty(gdoc.includeAuthor) as String,
-                noTimestamp: 'true',
-                noVersionStamp: resolveGroovydocProperty(gdoc.noVersionStamp) 
as String,
-                processScripts: resolveGroovydocProperty(gdoc.processScripts) 
as String,
-                includeMainForScripts: 
resolveGroovydocProperty(gdoc.includeMainForScripts) as String,
-                javaVersion: groovydocJavaVersion
-        )
-    }
 }
 
 tasks.register('docs', Sync).configure { Sync docTask ->
diff --git a/grails-data-mongodb/docs/build.gradle 
b/grails-data-mongodb/docs/build.gradle
index 81a0fca998..02de80eb95 100644
--- a/grails-data-mongodb/docs/build.gradle
+++ b/grails-data-mongodb/docs/build.gradle
@@ -31,13 +31,7 @@ ext {
     coreProjects = ['grails-datastore-core', 'grails-datamapping-core']
 }
 
-configurations {
-    documentation {
-        attributes {
-            attribute(Bundling.BUNDLING_ATTRIBUTE, (Bundling) 
(objects.named(Bundling, 'external')))
-        }
-    }
-}
+apply plugin: 'org.apache.grails.buildsrc.groovydoc'
 
 tasks.register('resolveMongodbVersion').configure { Task docTask ->
     docTask.group = 'documentation'
@@ -103,40 +97,12 @@ tasks.named('asciidoctor', AsciidoctorTask) { 
AsciidoctorTask it ->
     ]
 }
 
-// Compute the javaVersion string for groovydoc's JavaParser language level.
-// Requires Groovy 4.0.27+ (GROOVY-11668).
-String groovydocJavaVersion = "JAVA_${project.findProperty('javaVersion') ?: 
'17'}"
-
-// Resolve a Groovydoc task property that may be either a Gradle Property<T> 
or a plain value.
-Object resolveGroovydocProperty(Object value) {
-    if (value instanceof org.gradle.api.provider.Provider) {
-        return value.getOrNull()
-    }
-    return value
-}
-
 tasks.withType(Groovydoc).configureEach { Groovydoc groovydoc ->
     groovydoc.dependsOn(rootProject.subprojects
             .findAll { it.findProperty('gormApiDocs') }
             .collect { ":${it.name}:groovydoc" })
     groovydoc.docTitle = "GORM for MongoDB - $project.version"
-    groovydoc.footer = '''<!-- Matomo -->
-<script>
-    var _paq = window._paq = window._paq || [];
-    /* tracker methods like "setCustomDimension" should be called before 
"trackPageView" */
-    _paq.push(["setDoNotTrack", true]);
-    _paq.push(["disableCookies"]);
-    _paq.push(['trackPageView']);
-    _paq.push(['enableLinkTracking']);
-    (function() {
-        var u="https://analytics.apache.org/";;
-        _paq.push(['setTrackerUrl', u+'matomo.php']);
-        _paq.push(['setSiteId', '79']);
-        var d=document, g=d.createElement('script'), 
s=d.getElementsByTagName('script')[0];
-        g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
-    })();
-</script>
-<!-- End Matomo Code -->'''
+    groovydoc.includeAuthor = true
 
     def sourceFiles = coreProjects.collect {
         layout.projectDirectory.files("$it/src/main/groovy")
@@ -148,15 +114,8 @@ tasks.withType(Groovydoc).configureEach { Groovydoc 
groovydoc ->
 
     groovydoc.source = sourceFiles
     groovydoc.destinationDir = 
layout.buildDirectory.dir('combined-api/api').get().asFile
-    groovydoc.access = GroovydocAccess.PROTECTED
-    groovydoc.processScripts = false
-    groovydoc.includeMainForScripts = false
-    groovydoc.includeAuthor = true
     groovydoc.classpath = configurations.documentation
-    groovydoc.groovyClasspath += configurations.documentation
-    groovydoc.noVersionStamp = false
 
-    // Collect source directories for AntBuilder groovydoc
     List<File> groovydocSrcDirs = coreProjects.collect {
         layout.projectDirectory.dir("$it/src/main/groovy").asFile
     }
@@ -164,43 +123,6 @@ tasks.withType(Groovydoc).configureEach { Groovydoc 
groovydoc ->
             .findAll { sp -> sp.findProperty('gormApiDocs') }
             .each { sp -> groovydocSrcDirs << new File(sp.projectDir, 
'src/main/groovy') }
     groovydoc.ext.groovydocSourceDirs = groovydocSrcDirs
-
-    // Replace Gradle's built-in Groovydoc execution with AntBuilder to 
support javaVersion.
-    // See: https://github.com/apache/grails-core/issues/15385
-    groovydoc.actions.clear()
-    groovydoc.doLast { Groovydoc gdoc ->
-        def destDir = gdoc.destinationDir
-        destDir.mkdirs()
-
-        List<File> sourceDirs = (gdoc.ext.groovydocSourceDirs as 
List<File>).findAll { File f -> f.exists() }.unique()
-        if (sourceDirs.isEmpty()) {
-            logger.lifecycle("Skipping groovydoc for ${gdoc.name}: no source 
directories found")
-            return
-        }
-
-        ant.taskdef(
-                name: 'groovydoc',
-                classname: 'org.codehaus.groovy.ant.Groovydoc',
-                classpath: configurations.documentation.asPath
-        )
-
-        def sourcepath = sourceDirs.collect { File f -> f.absolutePath 
}.join(File.pathSeparator)
-
-        ant.groovydoc(
-                destdir: destDir.absolutePath,
-                sourcepath: sourcepath,
-                packagenames: '**.*',
-                doctitle: gdoc.docTitle ?: '',
-                footer: gdoc.footer ?: '',
-                access: 
resolveGroovydocProperty(gdoc.access)?.name()?.toLowerCase() ?: 'protected',
-                author: resolveGroovydocProperty(gdoc.includeAuthor) as String,
-                noTimestamp: 'true',
-                noVersionStamp: resolveGroovydocProperty(gdoc.noVersionStamp) 
as String,
-                processScripts: resolveGroovydocProperty(gdoc.processScripts) 
as String,
-                includeMainForScripts: 
resolveGroovydocProperty(gdoc.includeMainForScripts) as String,
-                javaVersion: groovydocJavaVersion
-        )
-    }
 }
 
 tasks.register('docs', Sync).configure { Sync docTask ->
diff --git a/grails-doc/build.gradle b/grails-doc/build.gradle
index 63382c712e..4f7c1d9d87 100644
--- a/grails-doc/build.gradle
+++ b/grails-doc/build.gradle
@@ -63,23 +63,6 @@ apply from: 
rootProject.layout.projectDirectory.file('gradle/docs-dependencies.g
 combinedGroovydoc.configure { Groovydoc gdoc ->
     gdoc.windowTitle = "Grails $projectVersion"
     gdoc.docTitle = "Grails $projectVersion"
-    gdoc.footer = '''<!-- Matomo -->
-<script>
-    var _paq = window._paq = window._paq || [];
-    /* tracker methods like "setCustomDimension" should be called before 
"trackPageView" */
-    _paq.push(["setDoNotTrack", true]);
-    _paq.push(["disableCookies"]);
-    _paq.push(['trackPageView']);
-    _paq.push(['enableLinkTracking']);
-    (function() {
-        var u="https://analytics.apache.org/";;
-        _paq.push(['setTrackerUrl', u+'matomo.php']);
-        _paq.push(['setSiteId', '79']);
-        var d=document, g=d.createElement('script'), 
s=d.getElementsByTagName('script')[0];
-        g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
-    })();
-</script>
-<!-- End Matomo Code -->'''
 
     def docProjects = rootProject.subprojects
             .findAll { it.findProperty('includeInApiDocs') }
diff --git a/grails-forge/gradle/doc-config.gradle 
b/grails-forge/gradle/doc-config.gradle
index 38b8e2a60d..4d22d4fba6 100644
--- a/grails-forge/gradle/doc-config.gradle
+++ b/grails-forge/gradle/doc-config.gradle
@@ -17,14 +17,10 @@
  *  under the License.
  */
 
-configurations.register('documentation') {
-    canBeConsumed = false
-    canBeResolved = true
-    attributes {
-        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, 
Category.LIBRARY))
-        attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, 
Bundling.EXTERNAL))
-        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 
Usage.JAVA_RUNTIME))
-    }
+apply plugin: 'org.apache.grails.buildsrc.groovydoc'
+
+grailsGroovydoc {
+    javaVersionEnabled = false
 }
 
 dependencies {
@@ -38,85 +34,4 @@ tasks.withType(Groovydoc).configureEach { Groovydoc gdoc ->
     gdoc.classpath += project.configurations.documentation
     gdoc.windowTitle = "${project.findProperty('pomArtifactId') ?: 
project.name} - $projectVersion"
     gdoc.docTitle = "${project.findProperty('pomArtifactId') ?: project.name} 
- $projectVersion"
-    gdoc.footer = '''<!-- Matomo -->
-<script>
-    var _paq = window._paq = window._paq || [];
-    /* tracker methods like "setCustomDimension" should be called before 
"trackPageView" */
-    _paq.push(["setDoNotTrack", true]);
-    _paq.push(["disableCookies"]);
-    _paq.push(['trackPageView']);
-    _paq.push(['enableLinkTracking']);
-    (function() {
-        var u="https://analytics.apache.org/";;
-        _paq.push(['setTrackerUrl', u+'matomo.php']);
-        _paq.push(['setSiteId', '79']);
-        var d=document, g=d.createElement('script'), 
s=d.getElementsByTagName('script')[0];
-        g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
-    })();
-</script>
-<!-- End Matomo Code -->'''
-    gdoc.access = GroovydocAccess.PROTECTED
-    gdoc.includeAuthor = false
-    gdoc.includeMainForScripts = false
-    gdoc.processScripts = false
-    gdoc.noTimestamp = true
-    gdoc.noVersionStamp = false
-
-    // Resolve a Groovydoc task property that may be either a Gradle 
Property<T> or a plain value.
-    Closure resolveGroovydocProperty = { Object value ->
-        if (value instanceof org.gradle.api.provider.Provider) {
-            return value.getOrNull()
-        }
-        return value
-    }
-
-    // Replace Gradle's built-in Groovydoc execution with AntBuilder for 
consistency
-    // with the main build. The javaVersion parameter is not available in 
Groovy 3.x
-    // (requires Groovy 4.0.27+, GROOVY-11668) - it will be added when forge 
upgrades.
-    // See: https://github.com/apache/grails-core/issues/15385
-    gdoc.actions.clear()
-    gdoc.doLast {
-        def destDir = gdoc.destinationDir
-        destDir.mkdirs()
-
-        List<File> sourceDirs = []
-        def sourceSetsExt = project.extensions.findByType(SourceSetContainer)
-        if (sourceSetsExt) {
-            def mainSS = sourceSetsExt.findByName('main')
-            if (mainSS) {
-                sourceDirs.addAll(mainSS.groovy.srcDirs.findAll { it.exists() 
})
-                sourceDirs.addAll(mainSS.java.srcDirs.findAll { it.exists() })
-            }
-        }
-        sourceDirs = sourceDirs.unique()
-
-        if (sourceDirs.isEmpty()) {
-            logger.lifecycle("Skipping groovydoc for ${gdoc.name}: no source 
directories found")
-            return
-        }
-
-        ant.taskdef(
-                name: 'groovydoc',
-                classname: 'org.codehaus.groovy.ant.Groovydoc',
-                classpath: configurations.documentation.asPath
-        )
-
-        def sourcepath = sourceDirs.collect { File f -> f.absolutePath 
}.join(File.pathSeparator)
-
-        // Note: javaVersion omitted - not supported in Groovy 3.x (forge uses 
Groovy $groovyVersion)
-        ant.groovydoc(
-                destdir: destDir.absolutePath,
-                sourcepath: sourcepath,
-                packagenames: '**.*',
-                windowtitle: gdoc.windowTitle ?: '',
-                doctitle: gdoc.docTitle ?: '',
-                footer: gdoc.footer ?: '',
-                access: 
resolveGroovydocProperty(gdoc.access)?.name()?.toLowerCase() ?: 'protected',
-                author: resolveGroovydocProperty(gdoc.includeAuthor) as String,
-                noTimestamp: resolveGroovydocProperty(gdoc.noTimestamp) as 
String,
-                noVersionStamp: resolveGroovydocProperty(gdoc.noVersionStamp) 
as String,
-                processScripts: resolveGroovydocProperty(gdoc.processScripts) 
as String,
-                includeMainForScripts: 
resolveGroovydocProperty(gdoc.includeMainForScripts) as String
-        )
-    }
 }
diff --git a/grails-gradle/gradle/docs-config.gradle 
b/grails-gradle/gradle/docs-config.gradle
index e3459998b6..8426b70a63 100644
--- a/grails-gradle/gradle/docs-config.gradle
+++ b/grails-gradle/gradle/docs-config.gradle
@@ -17,15 +17,8 @@
  *  under the License.
  */
 
-configurations.register('documentation') {
-    canBeConsumed = false
-    canBeResolved = true
-    attributes {
-        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, 
Category.LIBRARY))
-        attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, 
Bundling.EXTERNAL))
-        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 
Usage.JAVA_RUNTIME))
-    }
-}
+apply plugin: 'org.apache.grails.buildsrc.groovydoc'
+
 dependencies {
     add('documentation', platform(project(':grails-gradle-bom')))
     add('documentation', 'org.fusesource.jansi:jansi')
@@ -42,88 +35,10 @@ ext {
     includeInApiDocs = true
 }
 
-// Resolve a Groovydoc task property that may be either a Gradle Property<T> 
or a plain value.
-Object resolveGroovydocProperty(Object value) {
-    if (value instanceof org.gradle.api.provider.Provider) {
-        return value.getOrNull()
-    }
-    return value
-}
-
-// Compute the javaVersion string for groovydoc's JavaParser language level.
-// Requires Groovy 4.0.27+ (GROOVY-11668).
-String groovydocJavaVersion = "JAVA_${project.findProperty('javaVersion') ?: 
'17'}"
-
 TaskProvider<Groovydoc> groovydocTask = tasks.named('groovydoc', Groovydoc)
 groovydocTask.configure { Groovydoc it ->
     it.classpath = configurations.documentation
-    it.groovyClasspath = configurations.documentation
-    it.access = GroovydocAccess.PROTECTED
-    it.includeAuthor = false
-    it.includeMainForScripts = false
-    it.processScripts = false
-    it.noTimestamp = true
-    it.noVersionStamp = false
-    it.footer = '''<!-- Matomo -->
-<script>
-    var _paq = window._paq = window._paq || [];
-    /* tracker methods like "setCustomDimension" should be called before 
"trackPageView" */
-    _paq.push(["setDoNotTrack", true]);
-    _paq.push(["disableCookies"]);
-    _paq.push(['trackPageView']);
-    _paq.push(['enableLinkTracking']);
-    (function() {
-        var u="https://analytics.apache.org/";;
-        _paq.push(['setTrackerUrl', u+'matomo.php']);
-        _paq.push(['setSiteId', '79']);
-        var d=document, g=d.createElement('script'), 
s=d.getElementsByTagName('script')[0];
-        g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
-    })();
-</script>
-<!-- End Matomo Code -->'''
     it.destinationDir = project.file('build/docs/api')
-
-    // Replace Gradle's built-in Groovydoc execution with AntBuilder to 
support javaVersion.
-    // See: https://github.com/apache/grails-core/issues/15385
-    it.actions.clear()
-    it.doLast {
-        def destDir = it.destinationDir
-        destDir.mkdirs()
-
-        SourceSetContainer sourceSets = 
project.extensions.getByType(SourceSetContainer)
-        List<File> sourceDirs = ([] + sourceSets.main.groovy.srcDirs + 
sourceSets.main.java.srcDirs)
-                .findAll { File f -> f.exists() }
-                .unique()
-
-        if (sourceDirs.isEmpty()) {
-            logger.lifecycle("Skipping groovydoc for ${it.name}: no source 
directories found")
-            return
-        }
-
-        ant.taskdef(
-                name: 'groovydoc',
-                classname: 'org.codehaus.groovy.ant.Groovydoc',
-                classpath: configurations.documentation.asPath
-        )
-
-        def sourcepath = sourceDirs.collect { File f -> f.absolutePath 
}.join(File.pathSeparator)
-
-        ant.groovydoc(
-                destdir: destDir.absolutePath,
-                sourcepath: sourcepath,
-                packagenames: '**.*',
-                windowtitle: it.windowTitle ?: '',
-                doctitle: it.docTitle ?: '',
-                footer: it.footer ?: '',
-                access: 
resolveGroovydocProperty(it.access)?.name()?.toLowerCase() ?: 'protected',
-                author: resolveGroovydocProperty(it.includeAuthor) as String,
-                noTimestamp: resolveGroovydocProperty(it.noTimestamp) as 
String,
-                noVersionStamp: resolveGroovydocProperty(it.noVersionStamp) as 
String,
-                processScripts: resolveGroovydocProperty(it.processScripts) as 
String,
-                includeMainForScripts: 
resolveGroovydocProperty(it.includeMainForScripts) as String,
-                javaVersion: groovydocJavaVersion
-        )
-    }
 }
 
 tasks.named('javadoc').configure {


Reply via email to