Skip to content
Draft
8 changes: 8 additions & 0 deletions build-logic/plugins/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ gradlePlugin {
id = 'org.apache.grails.gradle.grails-code-style'
implementationClass = 'org.apache.grails.buildsrc.GrailsCodeStylePlugin'
}
register('groovydocEnhancer') {
id = 'org.apache.grails.buildsrc.groovydoc-enhancer'
implementationClass = 'org.apache.grails.buildsrc.GroovydocEnhancerPlugin'
}
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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.CompileStatic

import org.gradle.api.Plugin
import org.gradle.api.Project

@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) {
project.pluginManager.apply(GroovydocEnhancerPlugin)
project.extensions.getByType(GroovydocEnhancerExtension)
.footer.set(MATOMO_FOOTER)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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 Groovydoc Enhancer convention plugin.
*
* <p>This plugin replaces Gradle's built-in Groovydoc task execution with
* a direct AntBuilder invocation of the Groovy {@code org.codehaus.groovy.ant.Groovydoc}
* Ant task. This enables the {@code javaVersion} parameter (added in Groovy 4.0.27,
* GROOVY-11668) which controls the JavaParser language level used when parsing
* Java source files.</p>
*
* <p>When Gradle natively supports the {@code javaVersion} property
* (see <a href="https://github.com/gradle/gradle/issues/33659">gradle#33659</a>),
* set {@link #useAntBuilder} to {@code false} to revert to Gradle's built-in
* Groovydoc task execution while retaining all other configuration (footer,
* defaults, etc.).</p>
*
* @since 7.0.8
*/
@CompileStatic
class GroovydocEnhancerExtension {

/**
* 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

/**
* Whether to replace Gradle's built-in Groovydoc task execution with
* AntBuilder invocation. When {@code true} (default), the plugin clears
* the task's actions and replaces them with a {@code doLast} that uses
* AntBuilder. When {@code false}, the plugin only applies property
* defaults (footer, etc.) and lets Gradle's built-in task run normally.
*
* <p>Set to {@code false} when Gradle adds native {@code javaVersion}
* support (gradle/gradle#33659).</p>
*
* <p>Defaults to {@code true}.</p>
*/
final Property<Boolean> useAntBuilder

/**
* HTML footer appended to every generated groovydoc page. Useful for
* analytics scripts, copyright notices, or custom branding.
*
* <p>Defaults to an empty string (no footer).</p>
*/
final Property<String> footer

@Inject
GroovydocEnhancerExtension(ObjectFactory objects, Project project) {
javaVersion = objects.property(String).convention(
project.provider { "JAVA_${GradleUtils.findProperty(project, 'javaVersion') ?: '17'}" as String }
)
javaVersionEnabled = objects.property(Boolean).convention(true)
useAntBuilder = objects.property(Boolean).convention(true)
footer = objects.property(String).convention('')
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* 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.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.SourceSetContainer
import org.gradle.api.tasks.javadoc.Groovydoc

@CompileStatic
class GroovydocEnhancerPlugin implements Plugin<Project> {

@Override
void apply(Project project) {
GroovydocEnhancerExtension extension = project.extensions.create(
'groovydocEnhancer',
GroovydocEnhancerExtension,
project
)
registerDocumentationConfiguration(project)
configureGroovydocDefaults(project, extension)
configureAntBuilderExecution(project, extension)
}

private static void registerDocumentationConfiguration(Project project) {
if (project.configurations.names.contains('documentation')) {
return
}
project.configurations.register('documentation') {
it.canBeConsumed = false
it.canBeResolved = true
it.attributes {
it.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category, Category.LIBRARY))
it.attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling, Bundling.EXTERNAL))
it.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage, Usage.JAVA_RUNTIME))
}
}
}

@CompileDynamic
private static void configureGroovydocDefaults(Project project, GroovydocEnhancerExtension extension) {
project.tasks.withType(Groovydoc).configureEach {
it.includeAuthor.set(false)
it.includeMainForScripts.set(false)
it.processScripts.set(false)
it.noTimestamp = true
it.noVersionStamp = false
def footerValue = extension.footer.getOrElse('')
if (footerValue) {
it.footer = footerValue
}
if (project.configurations.names.contains('documentation')) {
it.groovyClasspath = project.configurations.getByName('documentation')
}
}
}

@CompileDynamic
private static void configureAntBuilderExecution(Project project, GroovydocEnhancerExtension extension) {
project.tasks.withType(Groovydoc).configureEach { gdoc ->
if (!extension.useAntBuilder.get()) {
return
}

gdoc.actions.clear()
gdoc.doLast {
def destDir = gdoc.destinationDir.tap { it.mkdirs() }
def sourceDirs = resolveSourceDirectories(gdoc, project)
if (sourceDirs.isEmpty()) {
project.logger.lifecycle(
'Skipping groovydoc for {}: no source directories found',
gdoc.name
)
return
}

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
)

def links = resolveLinks(gdoc)
def sourcepath = sourceDirs
.collect { it.absolutePath }
.join(File.pathSeparator)

def 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 (var 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()
}

List<File> sourceDirs = []
def sourceSets = project.extensions.findByType(SourceSetContainer)
if (sourceSets) {
def mainSS = sourceSets.findByName('main')
if (mainSS) {
sourceDirs.addAll(mainSS.groovy.srcDirs.findAll { it.exists() })
sourceDirs.addAll(mainSS.java.srcDirs.findAll { it.exists() })
}
}
sourceDirs.unique()
}

@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
}
}
Loading
Loading