OpenSearch/gradle/missing-javadoc.gradle

369 lines
12 KiB
Groovy

/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
import javax.annotation.Nullable
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.internal.jvm.Jvm
/**
* Checks for missing javadocs.
*/
// Original version of this file is ported from the render-javadoc code in Lucene,
// original code under the Apache Software Foundation under Apache 2.0 license
// See - https://github.com/apache/lucene-solr/tree/master/gradle/documentation/render-javadoc
allprojects {
ext {
scriptResources = { buildscript ->
return file(buildscript.sourceFile.absolutePath.replaceAll('.gradle$', ""))
}
}
}
def resources = scriptResources(buildscript)
allprojects {
plugins.withType(JavaPlugin) {
configurations {
missingdoclet
}
dependencies {
missingdoclet "org.opensearch:missing-doclet"
}
ext {
relativeDocPath = project.path.replaceFirst(/:\w+:/, "").replace(':', '/')
docroot = file("${buildDir}/site")
}
// TODO: Add missingJavadoc checks to precommit plugin
// See https://github.com/opensearch-project/OpenSearch/issues/221
// Currently the missingJavadoc task fails due to missing documentation
// across multiple modules. Once javadocs are added, we can
// add this task to precommit plugin.
tasks.withType(MissingJavadocTask).configureEach {
enabled = true
title = "${project.rootProject.name} ${project.name} API"
// Set up custom doclet.
dependsOn configurations.missingdoclet
docletpath = configurations.missingdoclet
}
tasks.withType(Javadoc).configureEach {
dependsOn missingJavadoc
}
tasks.register('missingJavadoc', MissingJavadocTask) {
description "This task validates and generates Javadoc API documentation for the main source code."
group "documentation"
taskResources = resources
dependsOn sourceSets.main.compileClasspath
classpath = sourceSets.main.compileClasspath
srcDirSet = sourceSets.main.java
outputDir = project.javadoc.destinationDir
}
}
}
// Below modules are temporarily excluded for missingJavadoc checks.
// Currently all these modules fail the check due to missing javadocs.
// See https://github.com/opensearch-project/OpenSearch/issues/221
// When you add javadocs for a module, please ensure the missingJavadoc check
// succeeds after removing that module from below list.
// You can then remove that module from this exclusion list,
// which will enforce javadoc validation on it for every incoming PR. For example,
// the client:rest module has javadoc checks enforced as it is not part of below list.
// Also different modules might need javadoc validations at different levels,
// for instance, for some test modules, we may only want to have javadoc at "package"
// level while for others (like server module) we may want to have it at "parameter" level.
// Currently everything is configured at parameter level (strictest).
configure([
project(":benchmarks"),
project(":build-tools"),
project(":build-tools:reaper"),
project(":client:benchmark"),
project(":client:client-benchmark-noop-api-plugin"),
project(":client:rest-high-level"),
project(":client:test"),
project(":distribution:tools:java-version-checker"),
project(":distribution:tools:keystore-cli"),
project(":distribution:tools:launchers"),
project(":distribution:tools:plugin-cli"),
project(":doc-tools"),
project(":example-plugins:custom-settings"),
project(":example-plugins:custom-significance-heuristic"),
project(":example-plugins:custom-suggester"),
project(":example-plugins:painless-allowlist"),
project(":example-plugins:rescore"),
project(":example-plugins:rest-handler"),
project(":example-plugins:script-expert-scoring"),
project(":libs:opensearch-cli"),
project(":libs:opensearch-core"),
project(":libs:opensearch-dissect"),
project(":libs:opensearch-geo"),
project(":libs:opensearch-grok"),
project(":libs:opensearch-nio"),
project(":libs:opensearch-plugin-classloader"),
project(":libs:opensearch-secure-sm"),
project(":libs:opensearch-ssl-config"),
project(":libs:opensearch-x-content"),
project(":modules:aggs-matrix-stats"),
project(":modules:analysis-common"),
project(":modules:geo"),
project(":modules:ingest-common"),
project(":modules:ingest-geoip"),
project(":modules:ingest-user-agent"),
project(":modules:lang-expression"),
project(":modules:lang-mustache"),
project(":modules:lang-painless"),
project(":modules:lang-painless:spi"),
project(":modules:mapper-extras"),
project(":modules:opensearch-dashboards"),
project(":modules:parent-join"),
project(":modules:percolator"),
project(":modules:rank-eval"),
project(":modules:reindex"),
project(":modules:repository-url"),
project(":modules:systemd"),
project(":modules:transport-netty4"),
project(":plugins:analysis-icu"),
project(":plugins:analysis-kuromoji"),
project(":plugins:analysis-nori"),
project(":plugins:analysis-phonetic"),
project(":plugins:analysis-smartcn"),
project(":plugins:analysis-stempel"),
project(":plugins:analysis-ukrainian"),
project(":plugins:discovery-azure-classic"),
project(":plugins:discovery-ec2"),
project(":plugins:discovery-ec2:qa:amazon-ec2"),
project(":plugins:discovery-gce"),
project(":plugins:discovery-gce:qa:gce"),
project(":plugins:ingest-attachment"),
project(":plugins:mapper-annotated-text"),
project(":plugins:mapper-murmur3"),
project(":plugins:mapper-size"),
project(":plugins:repository-azure"),
project(":plugins:repository-gcs"),
project(":plugins:repository-hdfs"),
project(":plugins:repository-s3"),
project(":plugins:store-smb"),
project(":plugins:transport-nio"),
project(":qa:die-with-dignity"),
project(":qa:os"),
project(":qa:wildfly"),
project(":rest-api-spec"),
project(":test:external-modules:test-delayed-aggs"),
project(":test:fixtures:azure-fixture"),
project(":test:fixtures:gcs-fixture"),
project(":test:fixtures:hdfs-fixture"),
project(":test:fixtures:old-elasticsearch"),
project(":test:fixtures:s3-fixture"),
project(":test:framework"),
project(":test:logger-usage")
]) {
project.tasks.withType(MissingJavadocTask) {
isExcluded = true
}
}
configure(project(":server")) {
project.tasks.withType(MissingJavadocTask) {
// TODO: bump to variable missing level after increasing javadoc coverage
javadocMissingLevel = "class"
}
}
class MissingJavadocTask extends DefaultTask {
@InputFiles
@SkipWhenEmpty
@IgnoreEmptyDirectories
@PathSensitive(PathSensitivity.RELATIVE)
SourceDirectorySet srcDirSet;
@OutputDirectory
File outputDir
@CompileClasspath
FileCollection classpath
@CompileClasspath
FileCollection docletpath
@Input
String title
@Input
boolean linksource = false
@Input
boolean relativeProjectLinks = false
@Input
String javadocMissingLevel = "parameter"
@Input
boolean isExcluded = false
// anything in these packages is checked with level=method. This allows iteratively fixing one package at a time.
@Input
List<String> javadocMissingMethod = []
// default is not to ignore any elements, should only be used to workaround split packages
@Input
List<String> javadocMissingIgnore = []
@Nullable
@Optional
@Input
def executable
@Input
def taskResources
/** Utility method to recursively collect all tasks with same name like this one that we depend on */
private Set findRenderTasksInDependencies() {
Set found = []
def collectDeps
collectDeps = { task -> task.taskDependencies.getDependencies(task).findAll{ it.name == this.name && it.enabled && !found.contains(it) }.each{
found << it
collectDeps(it)
}}
collectDeps(this)
return found
}
@TaskAction
void render() {
if(isExcluded) {
return
}
def srcDirs = srcDirSet.srcDirs.findAll { dir -> dir.exists() }
def optionsFile = project.file("${getTemporaryDir()}/javadoc-options.txt")
// create the directory, so relative link calculation knows that it's a directory:
outputDir.mkdirs();
def opts = []
opts << [ '-overview', project.file("${srcDirs[0]}/overview.html") ]
opts << [ '-sourcepath', srcDirs.join(File.pathSeparator) ]
if (project.getGroup().toString().startsWith('org.opensearch')) {
opts << [ '-subpackages', 'org.opensearch']
} else {
opts << [ '-subpackages', project.getGroup().toString()]
}
opts << [ '-d', outputDir ]
opts << '-protected'
opts << [ '-encoding', 'UTF-8' ]
opts << [ '-charset', 'UTF-8' ]
opts << [ '-docencoding', 'UTF-8' ]
opts << '-noindex'
opts << '-author'
opts << '-version'
if (linksource) {
opts << '-linksource'
}
opts << '-use'
opts << [ '-locale', 'en_US' ]
opts << [ '-windowtitle', title ]
opts << [ '-doctitle', title ]
if (!classpath.isEmpty()) {
opts << [ '-classpath', classpath.asPath ]
}
opts << [ '-tag', 'opensearch.experimental:a:WARNING: This API is experimental and might change in incompatible ways in the next release.' ]
opts << [ '-tag', 'opensearch.internal:a:NOTE: This API is for internal purposes only and might change in incompatible ways in the next release.' ]
opts << [ '-doclet', "org.opensearch.missingdoclet.MissingDoclet" ]
opts << [ '-docletpath', docletpath.asPath ]
opts << [ '--missing-level', javadocMissingLevel ]
if (javadocMissingIgnore) {
opts << [ '--missing-ignore', String.join(',', javadocMissingIgnore) ]
}
if (javadocMissingMethod) {
opts << [ '--missing-method', String.join(',', javadocMissingMethod) ]
}
opts << [ '-quiet' ]
opts << [ '--release', 11 ]
opts << '-Xdoclint:all,-missing'
// Temporary file that holds all javadoc options for the current task.
optionsFile.withWriter("UTF-8", { writer ->
// escapes an option with single quotes or whitespace to be passed in the options.txt file for
def escapeJavadocOption = { String s -> (s =~ /[ '"]/) ? ("'" + s.replaceAll(/[\\'"]/, /\\$0/) + "'") : s }
opts.each { entry ->
if (entry instanceof List) {
writer.write(entry.collect { escapeJavadocOption(it as String) }.join(" "))
} else {
writer.write(escapeJavadocOption(entry as String))
}
writer.write('\n')
}
})
def javadocCmd = {
if (executable == null) {
return Jvm.current().javadocExecutable
} else {
return project.file(executable)
}
}()
def outputFile = project.file("${getTemporaryDir()}/javadoc-output.txt")
def result
outputFile.withOutputStream { output ->
result = project.exec {
executable javadocCmd
// we want to capture both stdout and stderr to the same
// stream but gradle attempts to close these separately
// (it has two independent pumping threads) and it can happen
// that one still tries to write something when the other closed
// the underlying output stream.
def wrapped = new java.io.FilterOutputStream(output) {
public void close() {
// no-op. we close this stream manually.
}
}
standardOutput = wrapped
errorOutput = wrapped
args += [ "@${optionsFile}" ]
// -J flags can't be passed via options file... (an error "javadoc: error - invalid flag: -J-Xmx512m" occurs.)
args += [ "-J-Xmx512m" ]
// force locale to be "en_US" (fix for: https://bugs.openjdk.java.net/browse/JDK-8222793)
args += [ "-J-Duser.language=en", "-J-Duser.country=US" ]
ignoreExitValue true
}
}
if (result.getExitValue() != 0) {
// Pipe the output to console. Intentionally skips any encoding conversion
// and pumps raw bytes.
System.out.write(outputFile.bytes)
def cause
try {
result.rethrowFailure()
} catch (ex) {
cause = ex
}
throw new GradleException("Javadoc generation failed for ${project.path},\n Options file at: ${optionsFile}\n Command output at: ${outputFile}", cause)
}
}
}