diff --git a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java index 29abfde8a0a..899868955ff 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java @@ -389,19 +389,17 @@ class JavaConventions { private void configureDependencyFixes(Project project) { project.getDependencies().getComponents().all((details) -> { - // https://github.com/spring-projects/spring-framework/issues/37209 - // https://github.com/spring-projects/spring-graphql/issues/1515 - String group = details.getId().getGroup(); - if ("org.springframework".equals(group) || "org.springframework.graphql".equals(group)) { - addDocumentationVariant(project, details, "javadocElements", DocsType.JAVADOC, "javadoc"); - addDocumentationVariant(project, details, "sourcesElements", DocsType.SOURCES, "sources"); - } + addDocumentationVariant(project, details, "implicitjavadoc", "implicitjavadoc", "javadoc"); + addDocumentationVariant(project, details, "implicitsources", "implicitsources", "sources"); }); } private void addDocumentationVariant(Project project, ComponentMetadataDetails details, String name, String docsType, String classifier) { ModuleVersionIdentifier id = details.getId(); + if ("org.nodejs".equals(id.getGroup())) { + return; + } String file = "%s-%s-%s.jar".formatted(id.getName(), id.getVersion(), classifier); details.addVariant(name, (variant) -> { variant.attributes((attributes) -> addDocumentationAttributes(project, attributes, docsType)); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomResolver.java index 723d263772a..e3b4579ac51 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomResolver.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomResolver.java @@ -24,7 +24,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.function.Function; +import java.util.stream.Stream; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; @@ -43,13 +46,13 @@ import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.ImportedBom; import org.springframework.boot.build.bom.Library.Link; import org.springframework.boot.build.bom.Library.LinkType; +import org.springframework.boot.build.bom.Library.LinkedVersion; import org.springframework.boot.build.bom.Library.Module; import org.springframework.boot.build.bom.ResolvedBom.Bom; import org.springframework.boot.build.bom.ResolvedBom.Id; -import org.springframework.boot.build.bom.ResolvedBom.JavadocLink; -import org.springframework.boot.build.bom.ResolvedBom.Links; import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; import org.springframework.boot.build.xml.XmlDocument; +import org.springframework.util.Assert; /** * Creates a {@link ResolvedBom resolved bom}. @@ -58,6 +61,8 @@ import org.springframework.boot.build.xml.XmlDocument; */ class BomResolver { + private static final Set SKIPPED_OFFLINE_JAVADOC_LIBRARIES = Set.of("Spring Boot"); + private final ConfigurationContainer configurations; private final DependencyHandler dependencies; @@ -70,51 +75,37 @@ class BomResolver { this.documentBuilder = XmlDocument.builder(); } - ResolvedBom resolve(BomExtension bomExtension) { - List libraries = new ArrayList<>(); - for (Library library : bomExtension.getLibraries()) { - List managedDependencies = new ArrayList<>(); - List imports = new ArrayList<>(); - for (Group group : library.getGroups()) { - for (Module module : group.getModules()) { - Id id = new Id(group.getId(), module.getName(), library.getVersion().toString()); - managedDependencies.add(id); - } - for (ImportedBom imported : group.getBoms()) { - Bom bom = bomFrom( - resolveBom("%s:%s:%s".formatted(group.getId(), imported.name(), library.getVersion()))); - imports.add(bom); - } + ResolvedBom resolve(BomExtension bom) { + Id id = Id.parse(bom.getId()); + List resolvedLibraries = bom.getLibraries().stream().map(this::resolve).toList(); + Map> offlineJavadocLinks = getOfflineJavadocLinks(bom, resolvedLibraries); + return new ResolvedBom(id, resolvedLibraries, offlineJavadocLinks); + } + + private ResolvedLibrary resolve(Library library) { + List managedDependencies = new ArrayList<>(); + List imports = new ArrayList<>(); + for (Group group : library.getGroups()) { + for (Module module : group.getModules()) { + Id managedDependency = new Id(group.getId(), module.getName(), library.getVersion().toString()); + managedDependencies.add(managedDependency); + } + for (ImportedBom importedBom : group.getBoms()) { + String importedId = "%s:%s:%s".formatted(group.getId(), importedBom.name(), library.getVersion()); + imports.add(resolveBom(importedId)); } - List javadocLinks = javadocLinksOf(library).stream() - .map((link) -> new JavadocLink(URI.create(link.url(library.getVersion())), link.packages())) - .toList(); - ResolvedLibrary resolvedLibrary = new ResolvedLibrary(library.getName(), library.getVersion().toString(), - library.getVersionProperty(), managedDependencies, imports, new Links(javadocLinks)); - libraries.add(resolvedLibrary); } - String[] idComponents = bomExtension.getId().split(":"); - return new ResolvedBom(new Id(idComponents[0], idComponents[1], idComponents[2]), libraries); + return new ResolvedLibrary(library.getName(), library.getVersion().toString(), library.getVersionProperty(), + managedDependencies, imports); } - private List javadocLinksOf(Library library) { - List javadocLinks = library.getLinks(LinkType.JAVADOC); - return (javadocLinks != null) ? javadocLinks : Collections.emptyList(); - } - - Bom resolveMavenBom(String coordinates) { - return bomFrom(resolveBom(coordinates)); - } - - private File resolveBom(String coordinates) { + private File resolveBomFile(String coordinates) { Set artifacts = this.configurations .detachedConfiguration(this.dependencies.create(coordinates + "@pom")) .getResolvedConfiguration() .getResolvedArtifacts(); - if (artifacts.size() != 1) { - throw new IllegalStateException("Expected a single artifact but '%s' resolved to %d artifacts" - .formatted(coordinates, artifacts.size())); - } + Assert.state(artifacts.size() == 1, "Expected a single artifact but '%s' resolved to %d artifacts" + .formatted(coordinates, artifacts.size())); return artifacts.iterator().next().getFile(); } @@ -122,52 +113,61 @@ class BomResolver { try { Node bom = nodeFrom(bomFile); File parentBomFile = parentBomFile(bom); - Bom parent = null; - if (parentBomFile != null) { - parent = bomFrom(parentBomFile); - } + Bom parent = (parentBomFile != null) ? bomFrom(parentBomFile) : null; Properties properties = Properties.from(bom, this::nodeFrom); List dependencyNodes = bom.nodesAt("/project/dependencyManagement/dependencies/dependency"); List managedDependencies = new ArrayList<>(); List imports = new ArrayList<>(); for (Node dependency : dependencyNodes) { - String groupId = properties.replace(dependency.textAt("groupId")); - String artifactId = properties.replace(dependency.textAt("artifactId")); - String version = properties.replace(dependency.textAt("version")); - String classifier = properties.replace(dependency.textAt("classifier")); - String scope = properties.replace(dependency.textAt("scope")); - Bom importedBom = null; - if ("import".equals(scope)) { - String type = properties.replace(dependency.textAt("type")); - if ("pom".equals(type)) { - importedBom = bomFrom(resolveBom(groupId + ":" + artifactId + ":" + version)); - } - } + Id dependencyId = getDependencyId(dependency, properties); + Bom importedBom = (isPomImport(properties, dependency)) ? resolveBom(dependencyId.toString()) : null; if (importedBom != null) { imports.add(importedBom); } else { - managedDependencies.add(new Id(groupId, artifactId, version, classifier)); + managedDependencies.add(dependencyId); } } - String groupId = bom.textAt("/project/groupId"); - if ((groupId == null || groupId.isEmpty()) && parent != null) { - groupId = parent.id().groupId(); - } - String artifactId = bom.textAt("/project/artifactId"); - String version = bom.textAt("/project/version"); - if ((version == null || version.isEmpty()) && parent != null) { - version = parent.id().version(); - } - return new Bom(new Id(groupId, artifactId, version), parent, managedDependencies, imports); + Id bomId = getBomId(bom, parent); + return new Bom(bomId, parent, managedDependencies, imports); } catch (Exception ex) { throw new RuntimeException(ex); } } + private Id getDependencyId(Node dependency, Properties properties) { + String groupId = properties.replace(dependency.textAt("groupId")); + String artifactId = properties.replace(dependency.textAt("artifactId")); + String version = properties.replace(dependency.textAt("version")); + String classifier = properties.replace(dependency.textAt("classifier")); + return new Id(groupId, artifactId, version, classifier); + } + + private boolean isPomImport(Properties properties, Node dependency) { + String scope = properties.replace(dependency.textAt("scope")); + return "import".equals(scope) && "pom".equals(properties.replace(dependency.textAt("type"))); + } + + private Id getBomId(Node bom, Bom parent) { + String groupId = bom.textAt("/project/groupId"); + if ((groupId == null || groupId.isEmpty()) && parent != null) { + groupId = parent.id().groupId(); + } + String artifactId = bom.textAt("/project/artifactId"); + String version = bom.textAt("/project/version"); + if ((version == null || version.isEmpty()) && parent != null) { + version = parent.id().version(); + } + return new Id(groupId, artifactId, version); + } + + Bom resolveBom(String coordinates) { + return bomFrom(resolveBomFile(coordinates)); + } + private Node nodeFrom(String coordinates) { - return nodeFrom(resolveBom(coordinates)); + return nodeFrom(resolveBomFile(coordinates)); } private Node nodeFrom(File bomFile) { @@ -186,11 +186,44 @@ class BomResolver { String parentGroupId = parent.textAt("groupId"); String parentArtifactId = parent.textAt("artifactId"); String parentVersion = parent.textAt("version"); - return resolveBom(parentGroupId + ":" + parentArtifactId + ":" + parentVersion); + return resolveBomFile(parentGroupId + ":" + parentArtifactId + ":" + parentVersion); } return null; } + private Map> getOfflineJavadocLinks(BomExtension bom, List resolvedLibraries) { + Map> offlineJavadocLinks = new TreeMap<>(); + for (Library library : bom.getLibraries()) { + if (SKIPPED_OFFLINE_JAVADOC_LIBRARIES.contains(library.getName())) { + continue; + } + ResolvedLibrary resolvedLibrary = ResolvedLibrary.find(resolvedLibraries, library); + Set linkedDependencies = new TreeSet<>(resolvedLibrary.allDependencies().toList()); + library.getModuleLinks().forEach((module, moduleLinks) -> { + moduleLinks.byType().getOrDefault(LinkType.JAVADOC, Collections.emptyList()).forEach((link) -> { + String version = resolvedLibrary.moduleVersion(module); + List moduleNames = module.moduleNames(); + addOfflineJavadocLink(offlineJavadocLinks, link, version, moduleNames.stream()); + module.moduleNames() + .forEach((moduleName) -> linkedDependencies + .removeIf((candidate) -> candidate.artifactId().equals(moduleName))); + }); + }); + library.getLinks().byType().getOrDefault(LinkType.JAVADOC, Collections.emptyList()).forEach((link) -> { + Stream modulesNames = linkedDependencies.stream().map(Id::artifactId); + addOfflineJavadocLink(offlineJavadocLinks, link, library.getVersion(), modulesNames); + }); + } + return offlineJavadocLinks; + } + + private void addOfflineJavadocLink(Map> offlineJavadocLinks, Link link, Object version, + Stream moduleNames) { + URI uri = URI.create(link.url(new LinkedVersion(version))); + offlineJavadocLinks.computeIfAbsent(uri, (key) -> new ArrayList<>()) + .addAll(moduleNames.map((name) -> "%s-%s-javadoc.jar".formatted(name, version)).toList()); + } + private static final class Node { private final XPath xpath; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java index 74e236c122c..9b7845a8811 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java @@ -295,8 +295,7 @@ public abstract class CheckBom extends DefaultTask { List errors = new ArrayList<>(); BomAlignment alignsWithBom = library.getAlignsWithBom(); if (alignsWithBom != null) { - Bom mavenBom = this.bomResolver - .resolveMavenBom(alignsWithBom.getCoordinates() + ":" + library.getVersion()); + Bom mavenBom = this.bomResolver.resolveBom(alignsWithBom.getCoordinates() + ":" + library.getVersion()); checkDependencyManagementAlignment(resolvedLibrary, mavenBom, errors, alignsWithBom::exclude); } return errors; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java index 9f83645f9da..86edd94fd33 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java @@ -41,8 +41,9 @@ import org.springframework.util.Assert; * @author Andy Wilkinson * @param id the ID of the resolved bom * @param libraries the libraries declared in the bom + * @param offlineJavadocLinks offline javadoc links */ -public record ResolvedBom(Id id, List libraries) { +public record ResolvedBom(Id id, List libraries, Map> offlineJavadocLinks) { private static final JsonMapper jsonMapper; @@ -53,13 +54,7 @@ public record ResolvedBom(Id id, List libraries) { } public ResolvedLibrary library(Library library) { - String name = library.getName(); - List matching = libraries().stream() - .filter((candidate) -> candidate.name().equals(name)) - .toList(); - Assert.state(!matching.isEmpty(), () -> "No library found with name '%s'".formatted(name)); - Assert.state(matching.size() == 1, () -> "Multiple libraries found with name '%s'".formatted(name)); - return matching.get(0); + return ResolvedLibrary.find(libraries(), library); } public Map dependencyVersions() { @@ -71,6 +66,10 @@ public record ResolvedBom(Id id, List libraries) { return libraries().stream().flatMap(ResolvedLibrary::allDependencies); } + public void writeTo(Writer writer) { + jsonMapper.writeValue(writer, this); + } + public static ResolvedBom readFrom(File file) { try (FileReader reader = new FileReader(file)) { return jsonMapper.readValue(reader, ResolvedBom.class); @@ -80,12 +79,8 @@ public record ResolvedBom(Id id, List libraries) { } } - public void writeTo(Writer writer) { - jsonMapper.writeValue(writer, this); - } - public record ResolvedLibrary(String name, String version, String versionProperty, List managedDependencies, - List importedBoms, Links links) { + List importedBoms) { public String moduleVersion(LinkedModule linkedModule) { Set matching = allDependencies() @@ -102,6 +97,16 @@ public record ResolvedBom(Id id, List libraries) { return Stream.concat(managedDependencies().stream(), importedBoms().stream().flatMap(Bom::allDependencies)); } + public static ResolvedLibrary find(List libraries, Library library) { + String name = library.getName(); + List matching = libraries.stream() + .filter((candidate) -> candidate.name().equals(name)) + .toList(); + Assert.state(!matching.isEmpty(), () -> "No library found with name '%s'".formatted(name)); + Assert.state(matching.size() == 1, () -> "Multiple libraries found with name '%s'".formatted(name)); + return matching.get(0); + } + } public record Bom(Id id, Bom parent, List managedDependencies, List importedBoms) { @@ -153,13 +158,10 @@ public record ResolvedBom(Id id, List libraries) { return groupId() + ":" + artifactId(); } - } - - public record Links(List javadoc) { - - } - - public record JavadocLink(URI uri, List packages) { + public static Id parse(String value) { + String[] components = value.split(":"); + return new Id(components[0], components[1], components[2]); + } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/docs/AggregatedJavadoc.java b/buildSrc/src/main/java/org/springframework/boot/build/docs/AggregatedJavadoc.java new file mode 100644 index 00000000000..7382b46690a --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/docs/AggregatedJavadoc.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed 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.springframework.boot.build.docs; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.gradle.api.UncheckedIOException; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.FileCollection; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.javadoc.Javadoc; +import org.gradle.external.javadoc.StandardJavadocDocletOptions; + +import org.springframework.boot.build.bom.ResolvedBom; +import org.springframework.util.StringUtils; + +/** + * Specialized {@link Javadoc} task for aggregated javadoc generation. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +public abstract class AggregatedJavadoc extends Javadoc { + + private static final Set JAVADOC_LIST_FILES = Set.of("package-list", "element-list"); + + private static final Map BUNDLED_JAVADOC_LISTS; + static { + Map bundledJavadocLists = new LinkedHashMap<>(); + bundledJavadocLists.put(Pattern.compile("^tomcat-embed-core-.*-javadoc\\.jar$"), "tomcat"); + bundledJavadocLists.put(Pattern.compile("^log4j-api-.*-javadoc\\.jar$"), "log4j-api"); + bundledJavadocLists.put(Pattern.compile("^log4j-core-.*-javadoc\\.jar$"), "log4j-core"); + BUNDLED_JAVADOC_LISTS = Collections.unmodifiableMap(bundledJavadocLists); + } + + private static final List IGNORED_PACKAGES; + static { + List ignoredPackages = new ArrayList<>(); + ignoredPackages.add("com.datastax.oss.driver.api.core"); + ignoredPackages.add("com.datastax.oss.driver.api.core.config"); + ignoredPackages.add("kotlinx.serialization.json"); + ignoredPackages.add("org.apache.activemq.artemis.core.server.embedded"); + ignoredPackages.add("org.hibernate.engine.transaction.jta.platform.internal"); + IGNORED_PACKAGES = Collections.unmodifiableList(ignoredPackages); + } + + @Classpath + @InputFiles + public abstract ConfigurableFileCollection getResolvedBom(); + + @Classpath + @InputFiles + public abstract ConfigurableFileCollection getJavadocJars(); + + @Override + protected void generate() { + StandardJavadocDocletOptions options = (StandardJavadocDocletOptions) getOptions(); + options.doclet("io.spring.javaformat.doclet.OfflineLinksDoclet"); + options.addBooleanOption("quiet", true); + options.addBooleanOption("Xdoclint:all,-missing", true); + options.addBooleanOption("Werror", true); + options.links("https://docs.oracle.com/en/java/javase/17/docs/api/"); + configureOfflineLinks(options); + super.generate(); + } + + private void configureOfflineLinks(StandardJavadocDocletOptions options) { + ResolvedBom resolvedBom = ResolvedBom.readFrom(getResolvedBom().getSingleFile()); + File javadocListsDir = getProject().getLayout().getBuildDirectory().get().dir("docs/javadoclists").getAsFile(); + copyElementListResourceIfMissing(javadocListsDir, "javase"); + extractJavdocListFiles(javadocListsDir); + if (getProject().getGradle().getStartParameter().getLogLevel() == LogLevel.DEBUG) { + options.addBooleanOption("offlinelinks-debug", true); + } + options.addStringOption("-link-modularity-mismatch", "info"); + options.addStringOption("offlinelinks-ignore-packages", + IGNORED_PACKAGES.stream().collect(Collectors.joining(","))); + options.addStringOption("offlinelinks-source", new File(javadocListsDir, "@name@").getAbsolutePath()); + options.linksOffline("https://docs.oracle.com/en/java/javase/17/docs/api", "javase"); + resolvedBom.offlineJavadocLinks() + .forEach((url, jars) -> addOfflineLinksOption(options, javadocListsDir, url, jars)); + } + + private void addOfflineLinksOption(StandardJavadocDocletOptions options, File javadocListsDir, URI url, + List jars) { + for (Map.Entry bundled : BUNDLED_JAVADOC_LISTS.entrySet()) { + if (matchesBundled(bundled.getKey(), jars)) { + copyElementListResourceIfMissing(javadocListsDir, bundled.getValue()); + options.linksOffline(url.toString(), bundled.getValue()); + return; + } + } + String listOfJavadocJars = jars.stream() + .filter((jar) -> Files.isDirectory(javadocListsDir.toPath().resolve(jar))) + .collect(Collectors.joining(",")); + if (StringUtils.hasLength(listOfJavadocJars)) { + options.linksOffline(url.toString(), listOfJavadocJars); + } + } + + private boolean matchesBundled(Pattern pattern, List jars) { + return jars.stream().anyMatch((jar) -> pattern.matcher(jar).matches()); + } + + private void copyElementListResourceIfMissing(File javadocListsDir, String name) { + try { + Path directory = javadocListsDir.toPath().resolve(name); + if (!Files.exists(directory)) { + Files.createDirectories(directory); + } + Path file = directory.resolve("element-list"); + if (!Files.exists(file)) { + Files.copy(getClass().getResourceAsStream(name + "-element-list"), file); + } + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private void extractJavdocListFiles(File javadocListsDir) { + getJavadocJars().forEach((javadocJar) -> { + FileCollection source = getProject().zipTree(javadocJar).filter(this::isJavadocListFile); + File destination = new File(javadocListsDir, javadocJar.getName()); + getProject().copy((copy) -> copy.from(source).into(destination)); + }); + } + + private boolean isJavadocListFile(File file) { + return JAVADOC_LIST_FILES.contains(file.getName()); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/docs/ConfigureJavadocLinks.java b/buildSrc/src/main/java/org/springframework/boot/build/docs/ConfigureJavadocLinks.java deleted file mode 100644 index 81d52a91d4e..00000000000 --- a/buildSrc/src/main/java/org/springframework/boot/build/docs/ConfigureJavadocLinks.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2012-present the original author or authors. - * - * Licensed 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.springframework.boot.build.docs; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.gradle.api.Action; -import org.gradle.api.file.FileCollection; -import org.gradle.api.tasks.javadoc.Javadoc; -import org.gradle.external.javadoc.StandardJavadocDocletOptions; - -import org.springframework.boot.build.bom.ResolvedBom; -import org.springframework.boot.build.bom.ResolvedBom.JavadocLink; - -/** - * An {@link Action} to configure the links option of a {@link Javadoc} task. - * - * @author Andy Wilkinson - */ -public class ConfigureJavadocLinks implements Action { - - private final FileCollection resolvedBoms; - - private final Collection includedLibraries; - - public ConfigureJavadocLinks(FileCollection resolvedBoms, Collection includedLibraries) { - this.resolvedBoms = resolvedBoms; - this.includedLibraries = includedLibraries; - } - - @Override - public void execute(Javadoc javadoc) { - javadoc.options((options) -> { - if (options instanceof StandardJavadocDocletOptions standardOptions) { - configureLinks(standardOptions); - } - }); - } - - private void configureLinks(StandardJavadocDocletOptions options) { - ResolvedBom resolvedBom = ResolvedBom.readFrom(this.resolvedBoms.getSingleFile()); - List links = new ArrayList<>(); - links.add("https://docs.oracle.com/en/java/javase/17/docs/api/"); - links.add("https://jakarta.ee/specifications/platform/11/apidocs/"); - resolvedBom.libraries() - .stream() - .filter((candidate) -> this.includedLibraries.contains(candidate.name())) - .flatMap((library) -> library.links().javadoc().stream()) - .map(JavadocLink::uri) - .map(URI::toString) - .forEach(links::add); - options.setLinks(links); - } - -} diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/docs/javase-element-list b/buildSrc/src/main/resources/org/springframework/boot/build/docs/javase-element-list new file mode 100644 index 00000000000..9cf9ae2dc6e --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/docs/javase-element-list @@ -0,0 +1,284 @@ +module:java.base +java.io +java.lang +java.lang.annotation +java.lang.constant +java.lang.invoke +java.lang.module +java.lang.ref +java.lang.reflect +java.lang.runtime +java.math +java.net +java.net.spi +java.nio +java.nio.channels +java.nio.channels.spi +java.nio.charset +java.nio.charset.spi +java.nio.file +java.nio.file.attribute +java.nio.file.spi +java.security +java.security.cert +java.security.interfaces +java.security.spec +java.text +java.text.spi +java.time +java.time.chrono +java.time.format +java.time.temporal +java.time.zone +java.util +java.util.concurrent +java.util.concurrent.atomic +java.util.concurrent.locks +java.util.function +java.util.jar +java.util.random +java.util.regex +java.util.spi +java.util.stream +java.util.zip +javax.crypto +javax.crypto.interfaces +javax.crypto.spec +javax.net +javax.net.ssl +javax.security.auth +javax.security.auth.callback +javax.security.auth.login +javax.security.auth.spi +javax.security.auth.x500 +javax.security.cert +module:java.compiler +javax.annotation.processing +javax.lang.model +javax.lang.model.element +javax.lang.model.type +javax.lang.model.util +javax.tools +module:java.datatransfer +java.awt.datatransfer +module:java.desktop +java.applet +java.awt +java.awt.color +java.awt.desktop +java.awt.dnd +java.awt.event +java.awt.font +java.awt.geom +java.awt.im +java.awt.im.spi +java.awt.image +java.awt.image.renderable +java.awt.print +java.beans +java.beans.beancontext +javax.accessibility +javax.imageio +javax.imageio.event +javax.imageio.metadata +javax.imageio.plugins.bmp +javax.imageio.plugins.jpeg +javax.imageio.plugins.tiff +javax.imageio.spi +javax.imageio.stream +javax.print +javax.print.attribute +javax.print.attribute.standard +javax.print.event +javax.sound.midi +javax.sound.midi.spi +javax.sound.sampled +javax.sound.sampled.spi +javax.swing +javax.swing.border +javax.swing.colorchooser +javax.swing.event +javax.swing.filechooser +javax.swing.plaf +javax.swing.plaf.basic +javax.swing.plaf.metal +javax.swing.plaf.multi +javax.swing.plaf.nimbus +javax.swing.plaf.synth +javax.swing.table +javax.swing.text +javax.swing.text.html +javax.swing.text.html.parser +javax.swing.text.rtf +javax.swing.tree +javax.swing.undo +module:java.instrument +java.lang.instrument +module:java.logging +java.util.logging +module:java.management +java.lang.management +javax.management +javax.management.loading +javax.management.modelmbean +javax.management.monitor +javax.management.openmbean +javax.management.relation +javax.management.remote +javax.management.timer +module:java.management.rmi +javax.management.remote.rmi +module:java.naming +javax.naming +javax.naming.directory +javax.naming.event +javax.naming.ldap +javax.naming.ldap.spi +javax.naming.spi +module:java.net.http +java.net.http +module:java.prefs +java.util.prefs +module:java.rmi +java.rmi +java.rmi.dgc +java.rmi.registry +java.rmi.server +javax.rmi.ssl +module:java.scripting +javax.script +module:java.se +module:java.security.jgss +javax.security.auth.kerberos +org.ietf.jgss +module:java.security.sasl +javax.security.sasl +module:java.smartcardio +javax.smartcardio +module:java.sql +java.sql +javax.sql +module:java.sql.rowset +javax.sql.rowset +javax.sql.rowset.serial +javax.sql.rowset.spi +module:java.transaction.xa +javax.transaction.xa +module:java.xml +javax.xml +javax.xml.catalog +javax.xml.datatype +javax.xml.namespace +javax.xml.parsers +javax.xml.stream +javax.xml.stream.events +javax.xml.stream.util +javax.xml.transform +javax.xml.transform.dom +javax.xml.transform.sax +javax.xml.transform.stax +javax.xml.transform.stream +javax.xml.validation +javax.xml.xpath +org.w3c.dom +org.w3c.dom.bootstrap +org.w3c.dom.events +org.w3c.dom.ls +org.w3c.dom.ranges +org.w3c.dom.traversal +org.w3c.dom.views +org.xml.sax +org.xml.sax.ext +org.xml.sax.helpers +module:java.xml.crypto +javax.xml.crypto +javax.xml.crypto.dom +javax.xml.crypto.dsig +javax.xml.crypto.dsig.dom +javax.xml.crypto.dsig.keyinfo +javax.xml.crypto.dsig.spec +module:jdk.accessibility +com.sun.java.accessibility.util +module:jdk.attach +com.sun.tools.attach +com.sun.tools.attach.spi +module:jdk.charsets +module:jdk.compiler +com.sun.source.doctree +com.sun.source.tree +com.sun.source.util +com.sun.tools.javac +module:jdk.crypto.cryptoki +module:jdk.crypto.ec +module:jdk.dynalink +jdk.dynalink +jdk.dynalink.beans +jdk.dynalink.linker +jdk.dynalink.linker.support +jdk.dynalink.support +module:jdk.editpad +module:jdk.hotspot.agent +module:jdk.httpserver +com.sun.net.httpserver +com.sun.net.httpserver.spi +module:jdk.incubator.foreign +jdk.incubator.foreign +module:jdk.incubator.vector +jdk.incubator.vector +module:jdk.jartool +com.sun.jarsigner +jdk.security.jarsigner +module:jdk.javadoc +jdk.javadoc.doclet +module:jdk.jcmd +module:jdk.jconsole +com.sun.tools.jconsole +module:jdk.jdeps +module:jdk.jdi +com.sun.jdi +com.sun.jdi.connect +com.sun.jdi.connect.spi +com.sun.jdi.event +com.sun.jdi.request +module:jdk.jdwp.agent +module:jdk.jfr +jdk.jfr +jdk.jfr.consumer +module:jdk.jlink +module:jdk.jpackage +module:jdk.jshell +jdk.jshell +jdk.jshell.execution +jdk.jshell.spi +jdk.jshell.tool +module:jdk.jsobject +netscape.javascript +module:jdk.jstatd +module:jdk.localedata +module:jdk.management +com.sun.management +module:jdk.management.agent +module:jdk.management.jfr +jdk.management.jfr +module:jdk.naming.dns +module:jdk.naming.rmi +module:jdk.net +jdk.net +jdk.nio +module:jdk.nio.mapmode +jdk.nio.mapmode +module:jdk.sctp +com.sun.nio.sctp +module:jdk.security.auth +com.sun.security.auth +com.sun.security.auth.callback +com.sun.security.auth.login +com.sun.security.auth.module +module:jdk.security.jgss +com.sun.security.jgss +module:jdk.xml.dom +org.w3c.dom.css +org.w3c.dom.html +org.w3c.dom.stylesheets +org.w3c.dom.xpath +module:jdk.zipfs diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/docs/log4j-api-element-list b/buildSrc/src/main/resources/org/springframework/boot/build/docs/log4j-api-element-list new file mode 100644 index 00000000000..045218ff617 --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/docs/log4j-api-element-list @@ -0,0 +1,7 @@ +org.apache.logging.log4j +org.apache.logging.log4j.message +org.apache.logging.log4j.simple +org.apache.logging.log4j.spi +org.apache.logging.log4j.status +org.apache.logging.log4j.util +org.apache.logging.log4j.util.internal diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/docs/log4j-core-element-list b/buildSrc/src/main/resources/org/springframework/boot/build/docs/log4j-core-element-list new file mode 100644 index 00000000000..a69b4a8126e --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/docs/log4j-core-element-list @@ -0,0 +1,50 @@ +org.apache.logging.log4j.core +org.apache.logging.log4j.core.appender +org.apache.logging.log4j.core.appender.db +org.apache.logging.log4j.core.appender.db.jdbc +org.apache.logging.log4j.core.appender.mom +org.apache.logging.log4j.core.appender.mom.jeromq +org.apache.logging.log4j.core.appender.mom.kafka +org.apache.logging.log4j.core.appender.nosql +org.apache.logging.log4j.core.appender.rewrite +org.apache.logging.log4j.core.appender.rolling +org.apache.logging.log4j.core.appender.rolling.action +org.apache.logging.log4j.core.appender.routing +org.apache.logging.log4j.core.async +org.apache.logging.log4j.core.config +org.apache.logging.log4j.core.config.arbiters +org.apache.logging.log4j.core.config.builder.api +org.apache.logging.log4j.core.config.builder.impl +org.apache.logging.log4j.core.config.composite +org.apache.logging.log4j.core.config.json +org.apache.logging.log4j.core.config.plugins +org.apache.logging.log4j.core.config.plugins.convert +org.apache.logging.log4j.core.config.plugins.processor +org.apache.logging.log4j.core.config.plugins.util +org.apache.logging.log4j.core.config.plugins.validation +org.apache.logging.log4j.core.config.plugins.validation.constraints +org.apache.logging.log4j.core.config.plugins.validation.validators +org.apache.logging.log4j.core.config.plugins.visitors +org.apache.logging.log4j.core.config.properties +org.apache.logging.log4j.core.config.status +org.apache.logging.log4j.core.config.xml +org.apache.logging.log4j.core.config.yaml +org.apache.logging.log4j.core.filter +org.apache.logging.log4j.core.filter.mutable +org.apache.logging.log4j.core.impl +org.apache.logging.log4j.core.jackson +org.apache.logging.log4j.core.jmx +org.apache.logging.log4j.core.layout +org.apache.logging.log4j.core.lookup +org.apache.logging.log4j.core.net +org.apache.logging.log4j.core.net.ssl +org.apache.logging.log4j.core.osgi +org.apache.logging.log4j.core.parser +org.apache.logging.log4j.core.pattern +org.apache.logging.log4j.core.script +org.apache.logging.log4j.core.selector +org.apache.logging.log4j.core.time +org.apache.logging.log4j.core.tools +org.apache.logging.log4j.core.util +org.apache.logging.log4j.core.util.datetime +org.apache.logging.log4j.core.util.internal.instant diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/docs/tomcat-element-list b/buildSrc/src/main/resources/org/springframework/boot/build/docs/tomcat-element-list new file mode 100644 index 00000000000..59ec8d2217d --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/docs/tomcat-element-list @@ -0,0 +1,121 @@ +org.apache.catalina +org.apache.catalina.ant +org.apache.catalina.ant.jmx +org.apache.catalina.authenticator +org.apache.catalina.authenticator.jaspic +org.apache.catalina.connector +org.apache.catalina.core +org.apache.catalina.deploy +org.apache.catalina.filters +org.apache.catalina.ha +org.apache.catalina.ha.authenticator +org.apache.catalina.ha.backend +org.apache.catalina.ha.context +org.apache.catalina.ha.deploy +org.apache.catalina.ha.session +org.apache.catalina.ha.tcp +org.apache.catalina.loader +org.apache.catalina.manager +org.apache.catalina.manager.host +org.apache.catalina.manager.util +org.apache.catalina.mapper +org.apache.catalina.mbeans +org.apache.catalina.realm +org.apache.catalina.security +org.apache.catalina.servlets +org.apache.catalina.session +org.apache.catalina.ssi +org.apache.catalina.startup +org.apache.catalina.storeconfig +org.apache.catalina.tribes +org.apache.catalina.tribes.group +org.apache.catalina.tribes.group.interceptors +org.apache.catalina.tribes.io +org.apache.catalina.tribes.jmx +org.apache.catalina.tribes.membership +org.apache.catalina.tribes.membership.cloud +org.apache.catalina.tribes.tipis +org.apache.catalina.tribes.transport +org.apache.catalina.tribes.transport.nio +org.apache.catalina.tribes.util +org.apache.catalina.users +org.apache.catalina.util +org.apache.catalina.valves +org.apache.catalina.valves.rewrite +org.apache.catalina.webresources +org.apache.catalina.webresources.war +org.apache.coyote +org.apache.coyote.ajp +org.apache.coyote.http11 +org.apache.coyote.http11.filters +org.apache.coyote.http11.upgrade +org.apache.coyote.http2 +org.apache.el +org.apache.el.lang +org.apache.el.stream +org.apache.el.util +org.apache.jasper +org.apache.jasper.compiler +org.apache.jasper.compiler.tagplugin +org.apache.jasper.el +org.apache.jasper.optimizations +org.apache.jasper.runtime +org.apache.jasper.servlet +org.apache.jasper.tagplugins.jstl +org.apache.jasper.tagplugins.jstl.core +org.apache.jasper.util +org.apache.juli +org.apache.juli.logging +org.apache.naming +org.apache.naming.factory +org.apache.naming.java +org.apache.tomcat +org.apache.tomcat.buildutil +org.apache.tomcat.buildutil.translate +org.apache.tomcat.dbcp.dbcp2 +org.apache.tomcat.dbcp.dbcp2.cpdsadapter +org.apache.tomcat.dbcp.dbcp2.datasources +org.apache.tomcat.dbcp.dbcp2.managed +org.apache.tomcat.dbcp.pool2 +org.apache.tomcat.dbcp.pool2.impl +org.apache.tomcat.jdbc.naming +org.apache.tomcat.jdbc.pool +org.apache.tomcat.jdbc.pool.interceptor +org.apache.tomcat.jdbc.pool.jmx +org.apache.tomcat.jni +org.apache.tomcat.util +org.apache.tomcat.util.bcel +org.apache.tomcat.util.bcel.classfile +org.apache.tomcat.util.buf +org.apache.tomcat.util.collections +org.apache.tomcat.util.compat +org.apache.tomcat.util.concurrent +org.apache.tomcat.util.descriptor +org.apache.tomcat.util.descriptor.tagplugin +org.apache.tomcat.util.descriptor.tld +org.apache.tomcat.util.descriptor.web +org.apache.tomcat.util.digester +org.apache.tomcat.util.file +org.apache.tomcat.util.http +org.apache.tomcat.util.http.fileupload +org.apache.tomcat.util.http.fileupload.disk +org.apache.tomcat.util.http.fileupload.impl +org.apache.tomcat.util.http.fileupload.servlet +org.apache.tomcat.util.http.fileupload.util +org.apache.tomcat.util.http.fileupload.util.mime +org.apache.tomcat.util.http.parser +org.apache.tomcat.util.log +org.apache.tomcat.util.modeler +org.apache.tomcat.util.modeler.modules +org.apache.tomcat.util.net +org.apache.tomcat.util.net.jsse +org.apache.tomcat.util.net.openssl +org.apache.tomcat.util.net.openssl.ciphers +org.apache.tomcat.util.res +org.apache.tomcat.util.scan +org.apache.tomcat.util.security +org.apache.tomcat.util.threads +org.apache.tomcat.util.xreflection +org.apache.tomcat.websocket +org.apache.tomcat.websocket.pojo +org.apache.tomcat.websocket.server diff --git a/documentation/spring-boot-docs/build.gradle b/documentation/spring-boot-docs/build.gradle index fe3659c0cc6..fe262b81497 100644 --- a/documentation/spring-boot-docs/build.gradle +++ b/documentation/spring-boot-docs/build.gradle @@ -17,7 +17,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask -import org.springframework.boot.build.docs.ConfigureJavadocLinks +import org.springframework.boot.build.docs.AggregatedJavadoc import org.springframework.boot.build.optional.OptionalDependenciesPlugin plugins { @@ -34,6 +34,7 @@ plugins { description = "Spring Boot Docs" configurations { + doclet remoteSpringApplicationExample resolvedBom springApplicationExample @@ -83,6 +84,8 @@ plugins.withType(EclipsePlugin) { dependencies { compileOnly("org.hamcrest:hamcrest-core") + doclet("io.spring.javaformat:spring-javaformat-doclet:${javaFormatVersion}") + dokka(project(":core:spring-boot")) dokka(project(":module:spring-boot-resttestclient")) @@ -142,7 +145,7 @@ configurations { attributes { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) } - extendsFrom configurations.javadoc + extendsFrom configurations.runtimeClasspath } configureEach { resolutionStrategy { @@ -160,13 +163,24 @@ configurations { } } -def aggregatedJavadoc = tasks.register('aggregatedJavadoc', Javadoc) { - dependsOn configurations.resolvedBom +def aggregatedJavadoc = tasks.register('aggregatedJavadoc', AggregatedJavadoc) { + resolvedBom.from(configurations.resolvedBom) + javadocJars.from(configurations.runtimeClasspath.incoming.artifactView { + withVariantReselection() + attributes { + lenient = true + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION)); + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL)); + attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, "implicitjavadoc")); + } + }.files) destinationDir = project.file(project.layout.buildDirectory.dir("docs/javadoc")) source = configurations.javadocSource - classpath = configurations.javadocClasspath + classpath = configurations.runtimeClasspath include("**/*.java") options { + docletpath = configurations.doclet.files.toList() author = true docTitle = "Spring Boot ${project.version} API" memberLevel = "protected" @@ -175,7 +189,6 @@ def aggregatedJavadoc = tasks.register('aggregatedJavadoc', Javadoc) { use = true windowTitle = "Spring Boot ${project.version} API" } - doFirst(new ConfigureJavadocLinks(configurations.resolvedBom, ["Spring Framework", "Spring Security", "Tomcat"])) } project.rootProject.gradle.projectsEvaluated { diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index d41b4b4208d..f990bdb6f13 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -2583,7 +2583,7 @@ bom { .formatted(version.forAntora())) releaseNotes("https://github.com/spring-projects/spring-data-elasticsearch/releases/tag/{version}") } - module("spring-data-jpa") { + module("spring-data-jpa", ["spring-data-jpa", "spring-data-envers"]) { site("https://spring.io/projects/spring-data-jpa") github("https://github.com/spring-projects/spring-data-jpa") javadoc("https://docs.spring.io/spring-data/jpa/reference/{version}/api/java", @@ -2619,7 +2619,7 @@ bom { .formatted(version.forAntora())) releaseNotes("https://github.com/spring-projects/spring-data-neo4j/releases/tag/{version}") } - module("spring-data-relational") { + module("spring-data-relational", ["spring-data-relational", "spring-data-jdbc", "spring-data-r2dbc"]) { site("https://spring.io/projects/spring-data-relational") github("https://github.com/spring-projects/spring-data-relational") javadoc("https://docs.spring.io/spring-data/relational/reference/{version}/api/java",