import org.gradle.internal.jvm.Jvm /* * 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 * * http://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. */ // generate javadocs by calling javadoc tool // see https://docs.oracle.com/en/java/javase/11/tools/javadoc.html def resources = scriptResources(buildscript) allprojects { plugins.withType(JavaPlugin) { configurations { missingdoclet } dependencies { missingdoclet "org.apache.lucene.tools:missing-doclet" } ext { relativeDocPath = project.path.replaceFirst(/:\w+:/, "").replace(':', '/') } // We disable the default javadoc task and have our own // javadoc rendering task below. The default javadoc task // will just invoke 'renderJavadoc' (to allow people to call // conventional task name). tasks.matching { it.name == "javadoc" }.all { enabled = false dependsOn "renderJavadoc" } task renderJavadoc(type: RenderJavadocTask) { description "Generates Javadoc API documentation for each module. This directly invokes javadoc tool." group "documentation" taskResources = resources dependsOn sourceSets.main.compileClasspath classpath = sourceSets.main.compileClasspath srcDirSet = sourceSets.main.java outputDir = project.javadoc.destinationDir } if (project.path == ':lucene:luke' || !(project in rootProject.ext.mavenProjects)) { // These projects are not part of the public API so we don't render their javadocs // as part of the site's creation. A side-effect of this is that javadocs would not // be linted for these projects. To avoid this, we connect the regular javadoc task // to check so that everything is validated. project.tasks.getByName("check").dependsOn renderJavadoc } else { task renderSiteJavadoc(type: RenderJavadocTask) { description "Generates Javadoc API documentation for the site (relative links)." group "documentation" taskResources = resources dependsOn sourceSets.main.compileClasspath classpath = sourceSets.main.compileClasspath srcDirSet = sourceSets.main.java relativeProjectLinks = true // Place the documentation under the documentation directory. // docroot is defined in 'documentation.gradle' outputDir = project.docroot.toPath().resolve(project.relativeDocPath).toFile() } } } } // Set up titles and link up some offline docs for all documentation // (they may be unused but this doesn't do any harm). def javaJavadocPackages = rootProject.file("${resources}/java11/") def junitJavadocPackages = rootProject.file("${resources}/junit/") allprojects { project.tasks.withType(RenderJavadocTask) { title = "Lucene ${project.version} ${project.name} API" offlineLinks += [ "https://docs.oracle.com/en/java/javase/11/docs/api/": javaJavadocPackages, "https://junit.org/junit4/javadoc/4.12/": junitJavadocPackages ] luceneDocUrl = provider({ rootProject.luceneDocUrl }) // Set up custom doclet. dependsOn configurations.missingdoclet docletpath = configurations.missingdoclet } } // Configure project-specific tweaks and to-dos. configure(project(":lucene:analysis:common")) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing javadocs javadocMissingLevel = "class" } } configure([ project(":lucene:analysis:kuromoji"), project(":lucene:analysis:nori"), project(":lucene:analysis:opennlp"), project(":lucene:analysis:smartcn"), project(":lucene:benchmark"), project(":lucene:codecs"), project(":lucene:grouping"), project(":lucene:highlighter"), project(":lucene:luke"), project(":lucene:monitor"), project(":lucene:queries"), project(":lucene:queryparser"), project(":lucene:replicator"), project(":lucene:spatial-extras"), ]) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing javadocs javadocMissingLevel = "class" } } configure([ project(":lucene:analysis:icu"), project(":lucene:analysis:morfologik"), project(":lucene:analysis:phonetic"), project(":lucene:analysis:stempel"), project(":lucene:classification"), project(":lucene:demo"), project(":lucene:expressions"), project(":lucene:facet"), project(":lucene:join"), project(":lucene:spatial3d"), project(":lucene:suggest"), ]) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing @param tags javadocMissingLevel = "method" } } configure(project(":lucene:backward-codecs")) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing @param tags javadocMissingLevel = "method" } } configure(project(":lucene:test-framework")) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing javadocs javadocMissingLevel = "class" } } configure(project(":lucene:sandbox")) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing javadocs javadocMissingLevel = "class" } } configure(project(":lucene:spatial-test-fixtures")) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing javadocs javadocMissingLevel = "class" } } configure(project(":lucene:misc")) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing javadocs javadocMissingLevel = "class" } } configure(project(":lucene:core")) { project.tasks.withType(RenderJavadocTask) { // TODO: fix missing javadocs javadocMissingLevel = "class" // some packages are fixed already javadocMissingMethod = [ "org.apache.lucene.util.automaton", "org.apache.lucene.analysis.standard", "org.apache.lucene.analysis.tokenattributes", "org.apache.lucene.document", "org.apache.lucene.search.similarities", "org.apache.lucene.index", "org.apache.lucene.codecs", "org.apache.lucene.codecs.lucene50", "org.apache.lucene.codecs.lucene60", "org.apache.lucene.codecs.lucene80", "org.apache.lucene.codecs.lucene84", "org.apache.lucene.codecs.lucene86", "org.apache.lucene.codecs.lucene87", "org.apache.lucene.codecs.perfield" ] } } configure(project(':lucene:demo')) { project.tasks.withType(RenderJavadocTask) { // For the demo, we link the example source in the javadocs, as it's ref'ed elsewhere linksource = true } } // Add cross-project documentation task dependencies: // - each RenderJavaDocs task gets a dependency to all tasks with the same name in its dependencies // - the dependency is using dependsOn with a closure to enable lazy evaluation configure(subprojects) { project.tasks.withType(RenderJavadocTask) { task -> task.dependsOn { task.project.configurations.implementation.allDependencies.withType(ProjectDependency).collect { dep -> def otherProject = dep.dependencyProject return otherProject.tasks.findByName(task.name) } } } } class OfflineLink implements Serializable { @Input String url @InputDirectory @PathSensitive(PathSensitivity.RELATIVE) @IgnoreEmptyDirectories File location OfflineLink(String url, File location) { this.url = url this.location = location } } @CacheableTask class RenderJavadocTask extends DefaultTask { @InputFiles @PathSensitive(PathSensitivity.RELATIVE) @IgnoreEmptyDirectories @SkipWhenEmpty SourceDirectorySet srcDirSet; @OutputDirectory File outputDir @CompileClasspath FileCollection classpath @CompileClasspath FileCollection docletpath @Input String title @Input boolean linksource = false @Input boolean relativeProjectLinks = false @Internal Map offlineLinks = [:] // Computes cacheable inputs from the map in offlineLinks. @Nested List getCacheableOfflineLinks() { return offlineLinks.collect { url, location -> new OfflineLink(url, location) } } @Input @Optional final Property luceneDocUrl = project.objects.property(String) // default is to require full javadocs @Input String javadocMissingLevel = "parameter" // anything in these packages is checked with level=method. This allows iteratively fixing one package at a time. @Input List javadocMissingMethod = [] // default is not to ignore any elements, should only be used to workaround split packages @Input List javadocMissingIgnore = [] @Input @Optional ListProperty extraOpts = project.objects.listProperty(String) @Optional @Input final Property executable = project.objects.property(String).convention( project.provider { Jvm.current().javadocExecutable.toString() }) @InputDirectory @PathSensitive(PathSensitivity.RELATIVE) @IgnoreEmptyDirectories File 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 public void render() { def srcDirs = srcDirSet.sourceDirectories.filter { 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 = [] def overviewSourceSetDir = srcDirs.filter { dir -> project.file("${dir}/overview.html").exists() }.singleFile opts << [ '-overview', project.file("${overviewSourceSetDir}/overview.html") ] 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 << [ '-bottom', "Copyright © 2000-${project.buildYear} Apache Software Foundation. All Rights Reserved." ] opts << [ '-tag', 'lucene.experimental:a:WARNING: This API is experimental and might change in incompatible ways in the next release.' ] opts << [ '-tag', 'lucene.internal:a:NOTE: This API is for internal purposes only and might change in incompatible ways in the next release.' ] opts << [ '-tag', "lucene.spi:t:SPI Name (case-insensitive: if the name is 'htmlStrip', 'htmlstrip' can be used when looking up the service)." ] opts << [ '-doclet', "org.apache.lucene.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' ] // Add all extra options, if any. opts.addAll(extraOpts.orElse([]).get()) def allOfflineLinks = [:] allOfflineLinks.putAll(offlineLinks) // Resolve inter-project links: // - find all (enabled) tasks this tasks depends on (with same name), calling findRenderTasksInDependencies() // - sort the tasks preferring those whose project name equals 'core', then lexigraphical by path // - for each task get output dir to create relative or absolute link findRenderTasksInDependencies() .sort(false, Comparator.comparing { (it.project.name != 'core') as Boolean }.thenComparing(Comparator.comparing { it.path })) .each { otherTask -> def otherProject = otherTask.project // For relative links we compute the actual relative link between projects. if (relativeProjectLinks) { def pathTo = otherTask.outputDir.toPath().toAbsolutePath() def pathFrom = outputDir.toPath().toAbsolutePath() def relative = pathFrom.relativize(pathTo).toString().replace(File.separator, '/') opts << ['-link', relative] } else { // For absolute links, we determine the target URL by assembling the full URL (if base is available). def value = luceneDocUrl.getOrElse(null) if (value) { allOfflineLinks.put("${value}/${otherProject.relativeDocPath}/".toString(), otherTask.outputDir) } } } // Add offline links. allOfflineLinks.each { url, dir -> // Some sanity check/ validation here to ensure dir/package-list or dir/element-list is present. if (!project.file("$dir/package-list").exists() && !project.file("$dir/element-list").exists()) { throw new GradleException("Expected pre-rendered package-list or element-list at ${dir}.") } logger.info("Linking ${url} to ${dir}") opts << [ '-linkoffline', url, dir ] } opts << [ '--release', 17 ] opts << '-Xdoclint:all,-missing' // Increase Javadoc's heap. opts += [ "-J-Xmx512m" ] // Force locale to be "en_US" (fix for: https://bugs.openjdk.java.net/browse/JDK-8222793) opts += [ "-J-Duser.language=en", "-J-Duser.country=US" ] // -J options have to be passed on command line, they are not interpreted if passed via args file. def jOpts = opts.findAll { opt -> opt instanceof String && opt.startsWith("-J") } opts.removeAll(jOpts) // Collect all source files, for now excluding module descriptors. opts.addAll( srcDirs.collectMany { dir -> project.fileTree(dir: dir, include: "**/*.java", exclude: "**/module-info.java").files }.collect { it.toString() } ) // handle doc-files manually since in explicit source file mode javadoc does not copy them. srcDirs.each { File dir -> project.copy { into outputDir from(dir, { include "**/doc-files/**" }) } } // Temporary file that holds all javadoc options for the current task (except jOpts) 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 = project.file(executable.get()) logger.info("Javadoc executable used: ${javadocCmd}") project.quietExec { executable javadocCmd args += [ "@${optionsFile}" ] args += jOpts } // append some special table css, prettify css ant.concat(destfile: "${outputDir}/stylesheet.css", append: "true", fixlastline: "true", encoding: "UTF-8") { filelist(dir: taskResources, files: ["table_padding.css", "custom_styles.css", "prettify/prettify.css"].join(" ") ) } // append prettify to scripts ant.concat(destfile: "${outputDir}/script.js", append: "true", fixlastline: "true", encoding: "UTF-8") { filelist(dir: project.file("${taskResources}/prettify"), files: "prettify.js inject-javadocs.js") } ant.fixcrlf(srcdir: outputDir, includes: "stylesheet.css script.js", eol: "lf", fixlast: "true", encoding: "UTF-8") } }