From 8230a6c0f28c97be189a890fffa4c5e3361ac930 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 2 Sep 2026 12:57:05 -0700 Subject: [PATCH] Introduce module links to library DSL Update link support in the Gradle DSL so that module links can be directly modeled. See gh-51498 --- .../boot/build/AntoraConventions.java | 4 +- .../antora/AntoraAsciidocAttributes.java | 121 +++++------ .../boot/build/bom/BomExtension.java | 61 ++++-- .../boot/build/bom/BomPlugin.java | 8 +- .../boot/build/bom/CheckLinks.java | 51 +++-- .../boot/build/bom/Library.java | 17 +- .../boot/build/bom/ResolvedBom.java | 52 ++++- .../antora/AntoraAsciidocAttributesTests.java | 130 ++++++++---- .../boot/build/bom/LibraryTests.java | 7 +- .../bomr/InteractiveUpgradeResolverTests.java | 2 +- .../bom/bomr/UpgradeApplicatorTests.java | 4 +- .../boot/build/bom/bomr/UpgradeTests.java | 4 +- .../spring-boot-dependencies/build.gradle | 195 +++++++++++++----- 13 files changed, 451 insertions(+), 205 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java index c056f4b87dc..43369d8a12a 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java @@ -174,7 +174,7 @@ public class AntoraConventions { antoraTask.dependsOn(npmInstallTask, generateAntoraPlaybookTask); antoraTask.setPlaybook("antora-playbook.yml"); antoraTask.setUiBundleUrl(getUiBundleUrl(project)); - antoraTask.getArgs().set(project.provider(() -> getAntoraNpxArs(project, antoraTask))); + antoraTask.getArgs().set(project.provider(() -> getAntoraNpxArgs(project, antoraTask))); project.getPlugins() .withType(JavaBasePlugin.class, (javaBasePlugin) -> project.getTasks() @@ -182,7 +182,7 @@ public class AntoraConventions { .dependsOn(antoraTask)); } - private List getAntoraNpxArs(Project project, AntoraTask antoraTask) { + private List getAntoraNpxArgs(Project project, AntoraTask antoraTask) { logWarningIfNodeModulesInUserHome(project); StartParameter startParameter = project.getGradle().getStartParameter(); boolean showStacktrace = startParameter.getShowStacktrace().name().startsWith("ALWAYS"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java index bb0900478ea..8a2fa6fb858 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java @@ -20,13 +20,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.TreeMap; import java.util.stream.Collectors; import org.gradle.api.Project; @@ -34,8 +32,10 @@ import org.gradle.api.Project; import org.springframework.boot.build.artifacts.ArtifactRelease; import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.Library; +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.ResolvedBom; -import org.springframework.boot.build.bom.ResolvedBom.Bom; import org.springframework.boot.build.bom.ResolvedBom.Id; import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; import org.springframework.boot.build.properties.BuildProperties; @@ -61,6 +61,8 @@ public class AntoraAsciidocAttributes { private final List libraries; + private final ResolvedBom resolvedBom; + private final Map dependencyVersions; private final Map projectProperties; @@ -71,53 +73,11 @@ public class AntoraAsciidocAttributes { this.buildType = BuildProperties.get(project).buildType(); this.artifactRelease = ArtifactRelease.forProject(project); this.libraries = dependencyBom.getLibraries(); - this.dependencyVersions = dependencyVersionsOf(resolvedBom); + this.resolvedBom = resolvedBom; + this.dependencyVersions = resolvedBom.dependencyVersions(); this.projectProperties = project.getProperties(); } - private static Map dependencyVersionsOf(ResolvedBom resolvedBom) { - Map dependencyVersions = new HashMap<>(); - for (ResolvedLibrary library : resolvedBom.libraries()) { - dependencyVersions.putAll(dependencyVersionsOf(library.managedDependencies())); - for (Bom importedBom : library.importedBoms()) { - dependencyVersions.putAll(dependencyVersionsOf(importedBom)); - } - } - return dependencyVersions; - } - - private static Map dependencyVersionsOf(Bom bom) { - Map dependencyVersions = new HashMap<>(); - if (bom != null) { - dependencyVersions.putAll(dependencyVersionsOf(bom.managedDependencies())); - dependencyVersions.putAll(dependencyVersionsOf(bom.parent())); - for (Bom importedBom : bom.importedBoms()) { - dependencyVersions.putAll(dependencyVersionsOf(importedBom)); - } - } - return dependencyVersions; - } - - private static Map dependencyVersionsOf(Collection managedDependencies) { - Map dependencyVersions = new HashMap<>(); - for (Id managedDependency : managedDependencies) { - dependencyVersions.put(managedDependency.groupId() + ":" + managedDependency.artifactId(), - managedDependency.version()); - } - return dependencyVersions; - } - - AntoraAsciidocAttributes(String version, boolean latestVersion, BuildType buildType, List libraries, - Map dependencyVersions, Map projectProperties) { - this.version = version; - this.latestVersion = latestVersion; - this.buildType = buildType; - this.artifactRelease = ArtifactRelease.forVersion(version); - this.libraries = (libraries != null) ? libraries : Collections.emptyList(); - this.dependencyVersions = (dependencyVersions != null) ? dependencyVersions : Collections.emptyMap(); - this.projectProperties = (projectProperties != null) ? projectProperties : Collections.emptyMap(); - } - public Map get() { Map attributes = new LinkedHashMap<>(); Map internal = new LinkedHashMap<>(); @@ -126,7 +86,7 @@ public class AntoraAsciidocAttributes { addVersionAttributes(attributes, internal); addArtifactAttributes(attributes); addUrlJava(attributes); - addUrlLibraryLinkAttributes(attributes); + addLinksAttributes(attributes); addPropertyAttributes(attributes, internal); return attributes; } @@ -226,25 +186,10 @@ public class AntoraAsciidocAttributes { attributes.put("javadoc-location-javax-xml", "{url-javase-javadoc}/java.xml"); } - private void addUrlLibraryLinkAttributes(Map attributes) { - Map packageAttributes = new LinkedHashMap<>(); - this.libraries.forEach((library) -> { - library.getLinks().forEachLink((type, link) -> { - String linkRootName = (link.rootName() != null) ? link.rootName() : library.getLinkRootName(); - String linkName = "url-" + linkRootName + "-" + type.attributeName(); - attributes.put(linkName, link.url(library.getVersion())); - link.packages() - .stream() - .map(this::packageAttributeName) - .forEach((packageAttributeName) -> packageAttributes.put(packageAttributeName, - "{" + linkName + "}")); - }); - }); - attributes.putAll(packageAttributes); - } - - private String packageAttributeName(String packageName) { - return "javadoc-location-" + packageName.replace('.', '-'); + private void addLinksAttributes(Map attributes) { + LinkAttributes linkAttributes = new LinkAttributes(); + this.libraries.forEach(linkAttributes::add); + linkAttributes.copyTo(attributes); } private void addPropertyAttributes(Map attributes, Map internal) { @@ -272,4 +217,44 @@ public class AntoraAsciidocAttributes { return value; } + class LinkAttributes { + + private final Map urlAttributes = new TreeMap<>(); + + private final Map packageAttributes = new TreeMap<>(); + + void add(Library library) { + ResolvedLibrary resolvedLibrary = AntoraAsciidocAttributes.this.resolvedBom.library(library); + LinkedVersion libraryVersion = new LinkedVersion(library.getVersion()); + String libraryName = library.getLinkRootName(); + library.getLinks().forEachLink((type, link) -> addLinkAttributes(libraryName, type, link, libraryVersion)); + library.getModuleLinks().forEach((moduleName, links) -> { + Id resolvedModule = resolvedLibrary.module(moduleName); + LinkedVersion moduleVersion = new LinkedVersion(resolvedModule.version()); + links.forEachLink((type, link) -> addLinkAttributes(moduleName, type, link, moduleVersion)); + }); + } + + private void addLinkAttributes(String root, LinkType type, Link link, LinkedVersion version) { + String urlAttributeName = "url-" + root + "-" + type.attributeName(); + this.urlAttributes.put(urlAttributeName, link.url(version)); + link.packages() + .stream() + .map(this::packageAttributeName) + .forEach((packageAttributeName) -> this.packageAttributes.put(packageAttributeName, + "{" + urlAttributeName + "}")); + } + + private String packageAttributeName(String packageName) { + return "javadoc-location-" + packageName.replace('.', '-'); + } + + void copyTo(Map attributes) { + // URL attributes first since package attributes reference them + attributes.putAll(this.urlAttributes); + attributes.putAll(this.packageAttributes); + } + + } + } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java index 83352d13535..8259ed547c9 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java @@ -126,7 +126,7 @@ public class BomExtension { addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups, libraryHandler.upgradePolicy, libraryHandler.prohibitedVersions, firstParty, versionAlignment(libraryHandler), libraryHandler.alignWith.bomAlignment, libraryHandler.linkRootName, - new Links(libraryHandler.links))); + libraryHandler.links, libraryHandler.moduleLinks)); } private VersionAlignment versionAlignment(LibraryHandler libraryHandler) { @@ -224,7 +224,9 @@ public class BomExtension { private String linkRootName; - private final Map> links = new HashMap<>(); + private Links links; + + private Map moduleLinks; @Inject public LibraryHandler(Project project, String version) { @@ -274,15 +276,17 @@ public class BomExtension { action.execute(this.alignWith); } - public void links(Action action) { + public void links(Action action) { links(null, action); } - public void links(String linkRootName, Action action) { - LinksHandler handler = new LinksHandler(); + public void links(String linkRootName, Action action) { + LibraryLinksHandler handler = new LibraryLinksHandler(); action.execute(handler); this.linkRootName = linkRootName; - this.links.putAll(handler.links); + this.links = new Links(handler.links()); + this.moduleLinks = new HashMap<>(); + handler.moduleLinks().forEach((module, links) -> this.moduleLinks.put(module, new Links(links))); } public static class ProhibitedHandler { @@ -516,10 +520,14 @@ public class BomExtension { } - public static class LinksHandler { + public abstract static class LinksHandler { private final Map> links = new HashMap<>(); + Map> links() { + return this.links; + } + public void site(String linkTemplate) { site(asFactory(linkTemplate)); } @@ -560,10 +568,6 @@ public class BomExtension { add(LinkType.JAVADOC, linkFactory, packages); } - public void javadoc(String rootName, Function linkFactory, String... packages) { - add(rootName, LinkType.JAVADOC, linkFactory, packages); - } - public void releaseNotes(String linkTemplate) { releaseNotes(asFactory(linkTemplate)); } @@ -585,12 +589,7 @@ public class BomExtension { } private void add(LinkType type, Function linkFactory, String[] packages) { - add(null, type, linkFactory, packages); - } - - private void add(String rootName, LinkType type, Function linkFactory, - String[] packages) { - Link link = new Link(rootName, linkFactory, (packages != null) ? List.of(packages) : null); + Link link = new Link(linkFactory, (packages != null) ? List.of(packages) : null); this.links.computeIfAbsent(type, (key) -> new ArrayList<>()).add(link); } @@ -603,6 +602,34 @@ public class BomExtension { } + public static class LibraryLinksHandler extends LinksHandler { + + private final Map>> moduleLinks = new HashMap<>(); + + public Map>> moduleLinks() { + return this.moduleLinks; + } + + public void module(String moduleName, Closure closure) { + ModuleLinksHandler handler = new ModuleLinksHandler(); + closure.setDelegate(handler); + closure.setResolveStrategy(Closure.DELEGATE_FIRST); + closure.call(handler); + this.moduleLinks.computeIfAbsent(moduleName, (key) -> new HashMap<>()).putAll(handler.links()); + } + + public void module(String moduleName, Action action) { + ModuleLinksHandler handler = new ModuleLinksHandler(); + action.execute(handler); + this.moduleLinks.computeIfAbsent(moduleName, (key) -> new HashMap<>()).putAll(handler.links()); + } + + } + + public static class ModuleLinksHandler extends LinksHandler { + + } + public static class UpgradeHandler { private UpgradePolicy upgradePolicy; diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java index 1d1105bb140..e39b5348081 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java @@ -65,13 +65,15 @@ public class BomPlugin implements Plugin { TaskProvider checkBom = project.getTasks().register("bomrCheck", CheckBom.class, bom); checkBom.configure( (task) -> task.getResolvedBomFile().set(createResolvedBom.flatMap(CreateResolvedBom::getOutputFile))); + Configuration resolvedBom = project.getConfigurations().create("resolvedBom"); project.getTasks().named("check").configure((check) -> check.dependsOn(checkBom)); project.getTasks().register("bomrUpgrade", UpgradeBom.class, bom); project.getTasks().register("moveToSnapshots", MoveToSnapshots.class, bom); - project.getTasks().register("checkLinks", CheckLinks.class, bom); - Configuration resolvedBomConfiguration = project.getConfigurations().create("resolvedBom"); + project.getTasks() + .register("checkLinks", CheckLinks.class, bom, resolvedBom) + .configure((task) -> task.dependsOn(resolvedBom)); project.getArtifacts() - .add(resolvedBomConfiguration.getName(), createResolvedBom.map(CreateResolvedBom::getOutputFile), + .add(resolvedBom.getName(), createResolvedBom.map(CreateResolvedBom::getOutputFile), (artifact) -> artifact.builtBy(createResolvedBom)); new PublishingCustomizer(project, bom).customize(); } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java index ce68eaa11e0..6cada1c990e 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckLinks.java @@ -25,9 +25,15 @@ import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.gradle.api.DefaultTask; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.tasks.TaskAction; import org.gradle.internal.impldep.org.apache.http.client.config.CookieSpecs; +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.ResolvedBom.ResolvedLibrary; +import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestClient; @@ -46,13 +52,16 @@ public abstract class CheckLinks extends DefaultTask { private final BomExtension bom; + private final Configuration resolvedBom; + @Inject - public CheckLinks(BomExtension bom) { + public CheckLinks(BomExtension bom, Configuration resolvedBom) { this.bom = bom; + this.resolvedBom = resolvedBom; } @TaskAction - void releaseNotes() { + void check() { RequestConfig config = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(config).build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); @@ -60,19 +69,31 @@ public abstract class CheckLinks extends DefaultTask { .requestFactory(requestFactory) .defaultStatusHandler((status) -> true, NOOP_ERROR_HANDLER) .build(); - for (Library library : this.bom.getLibraries()) { - library.getLinks().forEachLink((type, link) -> { - URI uri; - try { - uri = new URI(link.url(library.getVersion())); - ResponseEntity response = restClient.head().uri(uri).retrieve().toEntity(String.class); - System.out.printf("[%3d] %s - %s (%s)%n", response.getStatusCode().value(), library.getName(), type, - uri); - } - catch (URISyntaxException ex) { - throw new RuntimeException(ex); - } - }); + this.bom.getLibraries().forEach((library) -> check(restClient, library)); + } + + private void check(RestClient restClient, Library library) { + ResolvedBom resolvedBom = ResolvedBom.readFrom(this.resolvedBom.getSingleFile()); + ResolvedLibrary resolvedLibrary = resolvedBom.library(library); + DependencyVersion libraryVersion = library.getVersion(); + String libraryName = library.getName(); + library.getLinks().forEachLink((type, link) -> check(restClient, type, link, libraryName, libraryVersion)); + library.getModuleLinks().forEach((module, links) -> { + String moduleName = "%s (%s)".formatted(library.getName(), module); + String moduleVersion = resolvedLibrary.module(module).version(); + links.forEachLink((type, link) -> check(restClient, type, link, moduleName, moduleVersion)); + }); + } + + private void check(RestClient restClient, LinkType type, Link link, String name, Object version) { + try { + URI uri = new URI(link.url(new LinkedVersion(version))); + ResponseEntity response = restClient.head().uri(uri).retrieve().toEntity(String.class); + int statusCode = response.getStatusCode().value(); + System.out.printf("[%3d] %s - %s (%s)%n", statusCode, name, type, uri); + } + catch (URISyntaxException ex) { + throw new RuntimeException(ex); } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java index 218d10d3efa..9a729520c52 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java @@ -85,6 +85,8 @@ public class Library { private final Links links; + private final Map moduleLinks; + /** * Create a new {@code Library} with the given {@code name}, {@code version}, and * {@code groups}. @@ -101,11 +103,13 @@ public class Library { * @param bomAlignment the bom, if any, that this library should align with * @param linkRootName the root name to use when generating link variable or * {@code null} to generate one based on the library {@code name} - * @param links a list of HTTP links relevant to the library + * @param links HTTP links relevant to the library + * @param moduleLinks HTTP links relevant to the library modules */ public Library(String name, String calendarName, DependencyVersion version, List groups, UpgradePolicy upgradePolicy, List prohibitedVersions, FirstParty firstParty, - VersionAlignment versionAlignment, BomAlignment bomAlignment, String linkRootName, Links links) { + VersionAlignment versionAlignment, BomAlignment bomAlignment, String linkRootName, Links links, + Map moduleLinks) { this.name = name; this.calendarName = (calendarName != null) ? calendarName : name; this.version = version; @@ -119,6 +123,7 @@ public class Library { this.bomAlignment = bomAlignment; this.linkRootName = (linkRootName != null) ? linkRootName : generateLinkRootName(name); this.links = (links != null) ? links : Links.empty(); + this.moduleLinks = (links != null) ? moduleLinks : Collections.emptyMap(); } private static String generateLinkRootName(String name) { @@ -177,6 +182,10 @@ public class Library { return this.links; } + public Map getModuleLinks() { + return this.moduleLinks; + } + public String getLinkUrl(LinkType type) { List links = getLinks(type); if (links == null || links.isEmpty()) { @@ -199,7 +208,7 @@ public class Library { public Library withVersion(DependencyVersion version) { return new Library(this.name, this.calendarName, version, this.groups, this.upgradePolicy, this.prohibitedVersions, this.firstParty, this.versionAlignment, this.bomAlignment, this.linkRootName, - this.links); + this.links, this.moduleLinks); } /** @@ -708,7 +717,7 @@ public class Library { } - public record Link(String rootName, Function factory, List packages) { + public record Link(Function factory, List packages) { private static final Pattern PACKAGE_EXPAND = Pattern.compile("^(.*)\\[(.*)\\]$"); 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 42560cf5161..18cbef18675 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 @@ -23,10 +23,15 @@ import java.io.UncheckedIOException; import java.io.Writer; import java.net.URI; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonInclude.Include; import tools.jackson.databind.json.JsonMapper; +import org.springframework.util.Assert; + /** * A resolved bom. * @@ -44,6 +49,25 @@ public record ResolvedBom(Id id, List libraries) { .build(); } + 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); + } + + public Map dependencyVersions() { + return allDependencies().collect(Collectors.toMap((id) -> id.groupId() + ":" + id.artifactId(), + (id) -> id.version(), (id1, id2) -> id1)); + } + + public Stream allDependencies() { + return libraries().stream().flatMap(ResolvedLibrary::allDependencies); + } + public static ResolvedBom readFrom(File file) { try (FileReader reader = new FileReader(file)) { return jsonMapper.readValue(reader, ResolvedBom.class); @@ -60,6 +84,28 @@ public record ResolvedBom(Id id, List libraries) { public record ResolvedLibrary(String name, String version, String versionProperty, List managedDependencies, List importedBoms, Links links) { + public Id module(String name) { + List matching = allDependencies().filter((candidate) -> candidate.artifactId().equals(name)).toList(); + Assert.state(!matching.isEmpty(), () -> "No module found with name '%s'".formatted(name)); + Assert.state(matching.size() == 1, () -> "Multiple artifacts found with name '%s'".formatted(name)); + return matching.get(0); + } + + public Stream allDependencies() { + return Stream.concat(managedDependencies().stream(), importedBoms().stream().flatMap(Bom::allDependencies)); + } + + } + + public record Bom(Id id, Bom parent, List managedDependencies, List importedBoms) { + + public Stream allDependencies() { + Stream managedAndImportedDependencies = Stream.concat(managedDependencies().stream(), + importedBoms().stream().flatMap(Bom::allDependencies)); + return (parent() != null) ? Stream.concat(parent().allDependencies(), managedAndImportedDependencies) + : managedAndImportedDependencies; + } + } public record Id(String groupId, String artifactId, String version, String classifier) implements Comparable { @@ -96,9 +142,9 @@ public record ResolvedBom(Id id, List libraries) { return builder.toString(); } - } - - public record Bom(Id id, Bom parent, List managedDependencies, List importedBoms) { + public String groupAndArtifactId() { + return groupId() + ":" + artifactId(); + } } diff --git a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java index 0627e543397..86beb6dc4c0 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java @@ -23,8 +23,12 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import org.gradle.api.Project; +import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.plugins.ExtraPropertiesExtension; import org.junit.jupiter.api.Test; +import org.springframework.boot.build.bom.BomExtension; import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library.BomAlignment; import org.springframework.boot.build.bom.Library.FirstParty; @@ -35,10 +39,16 @@ import org.springframework.boot.build.bom.Library.LinkedVersion; import org.springframework.boot.build.bom.Library.Links; import org.springframework.boot.build.bom.Library.ProhibitedVersion; import org.springframework.boot.build.bom.Library.VersionAlignment; +import org.springframework.boot.build.bom.ResolvedBom; +import org.springframework.boot.build.bom.ResolvedBom.Id; +import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.properties.BuildType; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; /** * Tests for {@link AntoraAsciidocAttributes}. @@ -50,57 +60,57 @@ class AntoraAsciidocAttributesTests { @Test void buildTypeWhenOpenSource() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("build-type", "opensource"); } @Test void buildTypeWhenCommercial() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.COMMERCIAL, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.COMMERCIAL, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("build-type", "commercial"); } @Test void githubRefWhenReleasedVersionIsTag() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("github-ref", "v1.2.3"); } @Test void githubRefWhenLatestSnapshotVersionIsMainBranch() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, - BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("github-ref", "main"); } @Test void githubRefWhenOlderSnapshotVersionIsBranch() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", false, - BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3-SNAPSHOT", false, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("github-ref", "1.2.x"); } @Test void githubRefWhenOlderSnapshotHotFixVersionIsBranch() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, - BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3.1-SNAPSHOT", false, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("github-ref", "1.2.3.x"); } @Test void versionReferenceFromLibrary() { Library library = mockLibrary(Collections.emptyMap()); - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, - BuildType.OPEN_SOURCE, List.of(library), mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3.1-SNAPSHOT", false, BuildType.OPEN_SOURCE, + List.of(library), mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("version-spring-framework", "1.2.3"); } @Test void versionReferenceFromSpringDataDependencyReleaseVersion() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions("3.2.5"), null); assertThat(attributes.get()).containsEntry("version-spring-data-mongodb", "3.2.5"); assertThat(attributes.get()).containsEntry("url-spring-data-mongodb-docs", @@ -111,7 +121,7 @@ class AntoraAsciidocAttributesTests { @Test void versionReferenceFromSpringDataDependencySnapshotVersion() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions("3.2.0-SNAPSHOT"), null); assertThat(attributes.get()).containsEntry("version-spring-data-mongodb", "3.2.0-SNAPSHOT"); assertThat(attributes.get()).containsEntry("url-spring-data-mongodb-docs", @@ -122,35 +132,35 @@ class AntoraAsciidocAttributesTests { @Test void versionNativeBuildTools() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), Map.of("nativeBuildToolsVersion", "3.4.5")); assertThat(attributes.get()).containsEntry("version-native-build-tools", "3.4.5"); } @Test void urlArtifactRepositoryWhenRelease() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.maven.apache.org/maven2"); } @Test void urlArtifactRepositoryWhenMilestone() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, BuildType.OPEN_SOURCE, - null, mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3-M1", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.maven.apache.org/maven2"); } @Test void urlArtifactRepositoryWhenSnapshot() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, - BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.spring.io/snapshot"); } @Test void artifactReleaseTypeWhenOpenSourceRelease() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.OPEN_SOURCE, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "release"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-release"); @@ -158,23 +168,23 @@ class AntoraAsciidocAttributesTests { @Test void artifactReleaseTypeWhenOpenSourceMilestone() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, BuildType.OPEN_SOURCE, - null, mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3-M1", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "milestone"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-milestone"); } @Test void artifactReleaseTypeWhenOpenSourceSnapshot() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, - BuildType.OPEN_SOURCE, null, mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "snapshot"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "opensource-snapshot"); } @Test void artifactReleaseTypeWhenCommercialRelease() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, BuildType.COMMERCIAL, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3", true, BuildType.COMMERCIAL, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "release"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-release"); @@ -182,7 +192,7 @@ class AntoraAsciidocAttributesTests { @Test void artifactReleaseTypeWhenCommercialMilestone() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, BuildType.COMMERCIAL, null, + AntoraAsciidocAttributes attributes = attributes("1.2.3-M1", true, BuildType.COMMERCIAL, null, mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "milestone"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-milestone"); @@ -190,8 +200,8 @@ class AntoraAsciidocAttributesTests { @Test void artifactReleaseTypeWhenCommercialSnapshot() { - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, BuildType.COMMERCIAL, - null, mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3-SNAPSHOT", true, BuildType.COMMERCIAL, null, + mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("artifact-release-type", "snapshot"); assertThat(attributes.get()).containsEntry("build-and-artifact-release-type", "commercial-snapshot"); } @@ -204,8 +214,8 @@ class AntoraAsciidocAttributesTests { links.put(LinkType.JAVADOC, singleLink((version) -> "https://example.com/api/" + version, "org.springframework.[core|util]")); Library library = mockLibrary(links); - AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, - BuildType.OPEN_SOURCE, List.of(library), mockDependencyVersions(), null); + AntoraAsciidocAttributes attributes = attributes("1.2.3.1-SNAPSHOT", false, BuildType.OPEN_SOURCE, + List.of(library), mockDependencyVersions(), null); assertThat(attributes.get()).containsEntry("url-spring-framework-site", "https://example.com/site/1.2.3") .containsEntry("url-spring-framework-docs", "https://example.com/docs/1.2.3") .containsEntry("url-spring-framework-javadoc", "https://example.com/api/1.2.3"); @@ -214,15 +224,31 @@ class AntoraAsciidocAttributesTests { .containsEntry("javadoc-location-org-springframework-util", "{url-spring-framework-javadoc}"); } + @Test + void urlLinksFromLibraryModule() { + Links links = new Links(Map.of(LinkType.SITE, singleLink((version) -> "https://example.com/site/" + version))); + Map moduleLinks = new LinkedHashMap<>(); + moduleLinks.put("example-module", new Links(Map.of(LinkType.JAVADOC, + singleLink((version) -> "https://example.com/moduleapi/" + version, "com.example")))); + Library library = mockLibrary(links, moduleLinks); + Map dependencyVersions = mockDependencyVersions(); + dependencyVersions.put("example-module", "3.4.5"); + AntoraAsciidocAttributes attributes = attributes("1.2.3.1-SNAPSHOT", false, BuildType.OPEN_SOURCE, + List.of(library), dependencyVersions, null); + assertThat(attributes.get()).containsEntry("url-spring-framework-site", "https://example.com/site/1.2.3") + .containsEntry("url-example-module-javadoc", "https://example.com/moduleapi/3.4.5"); + assertThat(attributes.get()).containsEntry("javadoc-location-com-example", "{url-example-module-javadoc}"); + } + private List singleLink(Function factory, String... packages) { - Link link = new Link(null, factory, List.of(packages)); + Link link = new Link(factory, List.of(packages)); return List.of(link); } @Test void linksFromProperties() { - Map attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, - null, mockDependencyVersions(), null) + Map attributes = attributes("1.2.3-SNAPSHOT", true, BuildType.OPEN_SOURCE, null, + mockDependencyVersions(), null) .get(); assertThat(attributes).containsEntry("include-java", "ROOT:example$java/org/springframework/boot/docs"); assertThat(attributes).containsEntry("url-spring-data-cassandra-site", @@ -231,7 +257,40 @@ class AntoraAsciidocAttributesTests { assertThat(keys.indexOf("include-java")).isLessThan(keys.indexOf("code-spring-boot-latest")); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private AntoraAsciidocAttributes attributes(String version, boolean latestVersion, BuildType buildType, + List libraries, Map dependencyVersions, Map projectProperties) { + libraries = (libraries != null) ? libraries : Collections.emptyList(); + projectProperties = (projectProperties != null) ? projectProperties : Collections.emptyMap(); + Project project = mock(); + ExtensionContainer extensions = mock(); + ExtraPropertiesExtension extraPropertiesExtension = mock(); + given(project.getVersion()).willReturn(version); + given(project.findProperty("latestVersion")).willReturn(String.valueOf(latestVersion)); + given(project.findProperty("spring.build-type")) + .willReturn((buildType == BuildType.OPEN_SOURCE) ? "oss" : "commercial"); + given(project.getProperties()).willReturn((Map) projectProperties); + given(project.getExtensions()).willReturn(extensions); + given(extensions.getExtraProperties()).willReturn(extraPropertiesExtension); + BomExtension dependencyBom = mock(); + given(dependencyBom.getLibraries()).willReturn(libraries); + ResolvedBom resolvedBom = mock(); + given(resolvedBom.dependencyVersions()).willReturn(dependencyVersions); + ResolvedLibrary resolvedLibrary = mock(); + given(resolvedBom.library(any())).willReturn(resolvedLibrary); + given(resolvedLibrary.module(any())).willAnswer((invocation) -> { + String moduleName = invocation.getArgument(0); + String moduleVersion = dependencyVersions.get(moduleName); + return new Id("com.example", moduleName, moduleVersion, null); + }); + return new AntoraAsciidocAttributes(project, dependencyBom, resolvedBom); + } + private Library mockLibrary(Map> links) { + return mockLibrary(new Links(links), Collections.emptyMap()); + } + + private Library mockLibrary(Links links, Map moduleLinks) { String name = "Spring Framework"; String calendarName = null; DependencyVersion version = DependencyVersion.parse("1.2.3"); @@ -241,9 +300,8 @@ class AntoraAsciidocAttributesTests { VersionAlignment versionAlignment = null; BomAlignment alignsWithBom = null; String linkRootName = null; - Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, firstParty, - versionAlignment, alignsWithBom, linkRootName, new Links(links)); - return library; + return new Library(name, calendarName, version, groups, null, prohibitedVersion, firstParty, versionAlignment, + alignsWithBom, linkRootName, links, moduleLinks); } private Map mockDependencyVersions() { diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java index a6e3661ba50..db37bd45e31 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/LibraryTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.build.bom; import java.util.Collections; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -52,8 +53,9 @@ class LibraryTests { BomAlignment alignsWithBom = null; String linkRootName = null; Links links = null; + Map moduleLinks = null; Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, firstParty, - versionAlignment, alignsWithBom, linkRootName, links); + versionAlignment, alignsWithBom, linkRootName, links, moduleLinks); assertThat(library.getLinkRootName()).isEqualTo("spring-framework"); } @@ -69,8 +71,9 @@ class LibraryTests { BomAlignment alignsWithBom = null; String linkRootName = "spring-data"; Links links = null; + Map moduleLinks = null; Library library = new Library(name, calendarName, version, groups, null, prohibitedVersion, firstParty, - versionAlignment, alignsWithBom, linkRootName, links); + versionAlignment, alignsWithBom, linkRootName, links, moduleLinks); assertThat(library.getLinkRootName()).isEqualTo("spring-data"); } diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolverTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolverTests.java index 0b5cfa1a61d..8e16abe7838 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolverTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolverTests.java @@ -46,7 +46,7 @@ class InteractiveUpgradeResolverTests { libaryUpdateResolver); List libraries = new ArrayList<>(); DependencyVersion version = DependencyVersion.parse("1.0.0"); - Library library = new Library("test", null, version, null, null, null, null, null, null, null, null); + Library library = new Library("test", null, version, null, null, null, null, null, null, null, null, null); libraries.add(library); List librariesToUpgrade = new ArrayList<>(); librariesToUpgrade.add(library); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java index 4d9cf53df13..b7f9cfafbab 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java @@ -51,7 +51,7 @@ class UpgradeApplicatorTests { File gradleProperties = new File(this.temp, "gradle.properties"); FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties); Library activeMq = new Library("ActiveMQ", null, DependencyVersion.parse("5.15.11"), null, null, null, null, - null, null, null, null); + null, null, null, null, null); new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()) .apply(new Upgrade(activeMq, activeMq.withVersion(DependencyVersion.parse("5.16")))); String bomContents = Files.readString(bom.toPath()); @@ -65,7 +65,7 @@ class UpgradeApplicatorTests { File gradleProperties = new File(this.temp, "gradle.properties"); FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties); Library kotlin = new Library("Kotlin", null, DependencyVersion.parse("1.3.70"), null, null, null, null, null, - null, null, null); + null, null, null, null); new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()) .apply(new Upgrade(kotlin, kotlin.withVersion(DependencyVersion.parse("1.4")))); Properties properties = new Properties(); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeTests.java index a58eb5dd7e7..b144ae5fecc 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeTests.java @@ -33,7 +33,7 @@ class UpgradeTests { @Test void createToRelease() { Library from = new Library("Test", null, DependencyVersion.parse("1.0.0"), null, null, null, null, null, null, - null, null); + null, null, null); Upgrade upgrade = new Upgrade(from, from.withVersion(DependencyVersion.parse("1.0.1"))); assertThat(upgrade.from().getNameAndVersion()).isEqualTo("Test 1.0.0"); assertThat(upgrade.to().getNameAndVersion()).isEqualTo("Test 1.0.1"); @@ -43,7 +43,7 @@ class UpgradeTests { @Test void createToSnapshot() { Library from = new Library("Test", null, DependencyVersion.parse("1.0.0"), null, null, null, null, null, null, - null, null); + null, null, null); Upgrade upgrade = new Upgrade(from, from.withVersion(DependencyVersion.parse("1.0.1-SNAPSHOT")), from.withVersion(DependencyVersion.parse("1.0.1"))); assertThat(upgrade.from().getNameAndVersion()).isEqualTo("Test 1.0.0"); diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index 9ea7e6c07f4..864f55f461d 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -376,7 +376,9 @@ bom { } links { releaseNotes(version -> "https://www.elastic.co/docs/release-notes/elasticsearch/clients/java/%s".formatted(version.toString("-"))) - javadoc("elasticsearch-java", version -> "https://javadoc.io/doc/co.elastic.clients/elasticsearch-java/%s/index.html".formatted(version), "co.elastic.clients.elasticsearch", "co.elastic.clients.transport") + module("elasticsearch-java") { + javadoc(version -> "https://javadoc.io/doc/co.elastic.clients/elasticsearch-java/%s/index.html".formatted(version), "co.elastic.clients.elasticsearch", "co.elastic.clients.transport") + } } } library("Flyway", "11.14.1") { @@ -880,10 +882,14 @@ bom { ] } links { - javadoc("jakarta-websocket-server", version -> "https://jakarta.ee/specifications/websocket/%s.%s/apidocs/server" - .formatted(version.major(), version.minor()), "jakarta.websocket.server") - javadoc("jakarta-websocket-client", version -> "https://jakarta.ee/specifications/websocket/%s.%s/apidocs/client" - .formatted(version.major(), version.minor()), "jakarta.websocket") + module("jakarta.websocket-api") { + javadoc( version -> "https://jakarta.ee/specifications/websocket/%s.%s/apidocs/server" + .formatted(version.major(), version.minor()), "jakarta.websocket.server") + } + module("jakarta.websocket-client-api") { + javadoc(version -> "https://jakarta.ee/specifications/websocket/%s.%s/apidocs/client" + .formatted(version.major(), version.minor()), "jakarta.websocket") + } } } library("Jakarta WS RS", "4.0.0") { @@ -1160,10 +1166,14 @@ bom { } links { site("https://junit.org/junit5") - javadoc("junit-platform-engine", version -> "https://junit.org/junit5/docs/%s/api/org.junit.platform.engine".formatted(version), "org.junit.platform") - javadoc("junit-jupiter-api", version -> "https://junit.org/junit5/docs/%s/api/org.junit.jupiter.api".formatted(version), "org.junit.jupiter.api") docs("https://junit.org/junit5/docs/{version}/user-guide") releaseNotes("https://junit.org/junit5/docs/{version}/release-notes") + module("junit-platform-engine") { + javadoc(version -> "https://junit.org/junit5/docs/%s/api/org.junit.platform.engine".formatted(version), "org.junit.platform") + } + module("junit-jupiter-api") { + javadoc(version -> "https://junit.org/junit5/docs/%s/api/org.junit.jupiter.api".formatted(version), "org.junit.jupiter.api") + } } } library("Kafka", "4.1.2") { @@ -1292,10 +1302,14 @@ bom { } links { site("https://logging.apache.org/log4j") - javadoc("log4j-api", version -> "https://logging.apache.org/log4j/%s.x/javadoc/log4j-api".formatted(version.major())) - javadoc("log4j-core", version -> "https://logging.apache.org/log4j/%s.x/javadoc/log4j-core".formatted(version.major()), "org.apache.logging.log4j.core") docs(version -> "https://logging.apache.org/log4j/%s.x/manual".formatted(version.major())) releaseNotes("https://github.com/apache/logging-log4j2/releases/tag/rel%2F{version}") + module("log4j-api") { + javadoc(version -> "https://logging.apache.org/log4j/%s.x/javadoc/log4j-api".formatted(version.major())) + } + module("log4j-core") { + javadoc(version -> "https://logging.apache.org/log4j/%s.x/javadoc/log4j-core".formatted(version.major()), "org.apache.logging.log4j.core") + } } } library("Logback", "1.5.38") { @@ -1550,15 +1564,27 @@ bom { } links { site("https://micrometer.io") - javadoc("micrometer-core", version -> "https://javadoc.io/doc/io.micrometer/micrometer-core/%s".formatted(version), "io.micrometer.core") - javadoc("micrometer-observation", version -> "https://javadoc.io/doc/io.micrometer/micrometer-observation/%s".formatted(version), "io.micrometer.observation") - javadoc("micrometer-registry-graphite", version -> "https://javadoc.io/doc/io.micrometer/micrometer-registry-graphite/%s".formatted(version), "io.micrometer.graphite") - javadoc("micrometer-registry-otlp", version -> "https://javadoc.io/doc/io.micrometer/micrometer-registry-otlp/%s".formatted(version), "io.micrometer.registry.otlp") - javadoc("micrometer-registry-jmx", version -> "https://javadoc.io/doc/io.micrometer/micrometer-registry-jmx/%s".formatted(version), "io.micrometer.jmx") - javadoc("micrometer-new-relic", version -> "https://javadoc.io/doc/io.micrometer/micrometer-registry-new-relic/%s".formatted(version), "io.micrometer.newrelic") docs(version -> "https://docs.micrometer.io/micrometer/reference/%s.%s" .formatted(version.major(), version.minor())) releaseNotes("https://github.com/micrometer-metrics/micrometer/releases/tag/v{version}") + module("micrometer-core") { + javadoc(version -> "https://javadoc.io/doc/io.micrometer/micrometer-core/%s".formatted(version), "io.micrometer.core") + } + module("micrometer-observation") { + javadoc(version -> "https://javadoc.io/doc/io.micrometer/micrometer-observation/%s".formatted(version), "io.micrometer.observation") + } + module("micrometer-registry-graphite") { + javadoc(version -> "https://javadoc.io/doc/io.micrometer/micrometer-registry-graphite/%s".formatted(version), "io.micrometer.graphite") + } + module("micrometer-registry-otlp") { + javadoc(version -> "https://javadoc.io/doc/io.micrometer/micrometer-registry-otlp/%s".formatted(version), "io.micrometer.registry.otlp") + } + module("micrometer-registry-jmx") { + javadoc(version -> "https://javadoc.io/doc/io.micrometer/micrometer-registry-jmx/%s".formatted(version), "io.micrometer.jmx") + } + module("micrometer-registry-new-relic") { + javadoc(version -> "https://javadoc.io/doc/io.micrometer/micrometer-registry-new-relic/%s".formatted(version), "io.micrometer.newrelic") + } } } library("Micrometer Tracing", "1.6.7") { @@ -1610,10 +1636,14 @@ bom { } links { site("https://github.com/mongodb/mongo-java-driver") - // Mongo has split packages so we can't use them - javadoc("mongodb-driver-core", version -> "https://mongodb.github.io/mongo-java-driver/%s.%s/apidocs/mongodb-driver-core".formatted(version.major(), version.minor())) - javadoc("mongodb-driver-sync", version -> "https://mongodb.github.io/mongo-java-driver/%s.%s/apidocs/mongodb-driver-sync".formatted(version.major(), version.minor())) releaseNotes("https://github.com/mongodb/mongo-java-driver/releases/tag/r{version}") + // Mongo has split packages so we can't link by package name + module("mongodb-driver-core") { + javadoc(version -> "https://mongodb.github.io/mongo-java-driver/%s.%s/apidocs/mongodb-driver-core".formatted(version.major(), version.minor())) + } + module("mongodb-driver-sync") { + javadoc(version -> "https://mongodb.github.io/mongo-java-driver/%s.%s/apidocs/mongodb-driver-sync".formatted(version.major(), version.minor())) + } } } library("MSSQL JDBC", "13.2.1.jre11") { @@ -1713,13 +1743,25 @@ bom { } links { site("https://github.com/open-telemetry/opentelemetry-java") - javadoc("opentelemetry-api", version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-api/%s".formatted(version), "io.opentelemetry.api") - javadoc("opentelemetry-context", version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-context/%s".formatted(version), "io.opentelemetry.context") - javadoc("opentelemetry-sdk-common", version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-common/%s".formatted(version), "io.opentelemetry.sdk.common", "io.opentelemetry.sdk.resources") - javadoc("opentelemetry-sdk-logs", version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-logs/%s".formatted(version), "io.opentelemetry.sdk.logs") - javadoc("opentelemetry-sdk-metrics", version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-metrics/%s".formatted(version), "io.opentelemetry.sdk.metrics") - javadoc("opentelemetry-sdk-trace", version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-trace/%s".formatted(version), "io.opentelemetry.sdk.trace") releaseNotes("https://github.com/open-telemetry/opentelemetry-java/releases/tag/v{version}") + module("opentelemetry-api") { + javadoc(version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-api/%s".formatted(version), "io.opentelemetry.api") + } + module("opentelemetry-context") { + javadoc(version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-context/%s".formatted(version), "io.opentelemetry.context") + } + module("opentelemetry-sdk-common") { + javadoc(version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-common/%s".formatted(version), "io.opentelemetry.sdk.common", "io.opentelemetry.sdk.resources") + } + module("opentelemetry-sdk-logs") { + javadoc(version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-logs/%s".formatted(version), "io.opentelemetry.sdk.logs") + } + module("opentelemetry-sdk-metrics") { + javadoc(version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-metrics/%s".formatted(version), "io.opentelemetry.sdk.metrics") + } + module("opentelemetry-sdk-trace") { + javadoc(version -> "https://javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-trace/%s".formatted(version), "io.opentelemetry.sdk.trace") + } } } library("Oracle Database", "23.9.0.25.07") { @@ -1801,8 +1843,10 @@ bom { } links { site("https://github.com/prometheus/client_java") - javadoc("prometheus-metrics-tracer-common", (version) -> "https://javadoc.io/doc/io.prometheus/prometheus-metrics-tracer-common/%s".formatted(version), "io.prometheus.metrics.tracer.common") releaseNotes("https://github.com/prometheus/client_java/releases/tag/v{version}") + module("prometheus-metrics-tracer-common") { + javadoc((version) -> "https://javadoc.io/doc/io.prometheus/prometheus-metrics-tracer-common/%s".formatted(version), "io.prometheus.metrics.tracer.common") + } } } library("Prometheus Simpleclient", "0.16.0") { @@ -1811,8 +1855,10 @@ bom { } links { site("https://github.com/prometheus/client_java") - javadoc("prometheus-simpleclient-tracer-common", (version) -> "https://javadoc.io/doc/io.prometheus/simpleclient_tracer_common/%s".formatted(version), "io.prometheus.client.exemplars.tracer.common") releaseNotes("https://github.com/prometheus/client_java/releases/tag/parent-{version}") + module("simpleclient_tracer_common") { + javadoc((version) -> "https://javadoc.io/doc/io.prometheus/simpleclient_tracer_common/%s".formatted(version), "io.prometheus.client.exemplars.tracer.common") + } } } library("Pulsar", "4.1.3") { @@ -2638,30 +2684,75 @@ bom { } links { docs("https://java.testcontainers.org") - javadoc("testcontainers", version -> "https://javadoc.io/doc/org.testcontainers/testcontainers/%s".formatted(version), "org.testcontainers") - javadoc("testcontainers-activemq", version -> "https://javadoc.io/doc/org.testcontainers/activemq/%s".formatted(version), "org.testcontainers.activemq") - javadoc("testcontainers-cassandra", version -> "https://javadoc.io/doc/org.testcontainers/cassandra/%s".formatted(version)) - javadoc("testcontainers-clickhouse", version -> "https://javadoc.io/doc/org.testcontainers/clickhouse/%s".formatted(version), "org.testcontainers.clickhouse") - javadoc("testcontainers-couchbase", version -> "https://javadoc.io/doc/org.testcontainers/couchbase/%s".formatted(version), "org.testcontainers.couchbase") - javadoc("testcontainers-elasticsearch", version -> "https://javadoc.io/doc/org.testcontainers/elasticsearch/%s".formatted(version), "org.testcontainers.elasticsearch") - javadoc("testcontainers-grafana", version -> "https://javadoc.io/doc/org.testcontainers/grafana/%s".formatted(version), "org.testcontainers.grafana") - javadoc("testcontainers-jdbc", version -> "https://javadoc.io/doc/org.testcontainers/jdbc/%s".formatted(version)) - javadoc("testcontainers-junit-jupiter", version -> "https://javadoc.io/doc/org.testcontainers/junit-jupiter/%s".formatted(version), "org.testcontainers.junit.jupiter") - javadoc("testcontainers-kafka", version -> "https://javadoc.io/doc/org.testcontainers/kafka/%s".formatted(version), "org.testcontainers.kafka") - javadoc("testcontainers-mariadb", version -> "https://javadoc.io/doc/org.testcontainers/mariadb/%s".formatted(version)) - javadoc("testcontainers-mongodb", version -> "https://javadoc.io/doc/org.testcontainers/mongodb/%s".formatted(version)) - javadoc("testcontainers-mssqlserver", version -> "https://javadoc.io/doc/org.testcontainers/mssqlserver/%s".formatted(version)) - javadoc("testcontainers-mysql", version -> "https://javadoc.io/doc/org.testcontainers/mysql/%s".formatted(version)) - javadoc("testcontainers-neo4j", version -> "https://javadoc.io/doc/org.testcontainers/neo4j/%s".formatted(version)) - javadoc("testcontainers-oracle-xe", version -> "https://javadoc.io/doc/org.testcontainers/oracle-xe/%s".formatted(version)) - javadoc("testcontainers-oracle-free", version -> "https://javadoc.io/doc/org.testcontainers/oracle-free/%s".formatted(version), "org.testcontainers.oracle") - javadoc("testcontainers-postgresql", version -> "https://javadoc.io/doc/org.testcontainers/postgresql/%s".formatted(version)) - javadoc("testcontainers-pulsar", version -> "https://javadoc.io/doc/org.testcontainers/pulsar/%s".formatted(version)) - javadoc("testcontainers-rabbitmq", version -> "https://javadoc.io/doc/org.testcontainers/rabbitmq/%s".formatted(version)) - javadoc("testcontainers-redpanda", version -> "https://javadoc.io/doc/org.testcontainers/redpanda/%s".formatted(version), "org.testcontainers.redpanda") - javadoc("testcontainers-r2dbc", version -> "https://javadoc.io/doc/org.testcontainers/r2dbc/%s".formatted(version), "org.testcontainers.r2dbc") releaseNotes("https://github.com/testcontainers/testcontainers-java/releases/tag/{version}") site("https://java.testcontainers.org") + module("testcontainers") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/testcontainers/%s".formatted(version), "org.testcontainers") + } + module("testcontainers-activemq") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/activemq/%s".formatted(version), "org.testcontainers.activemq") + } + module("testcontainers-cassandra") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/cassandra/%s".formatted(version)) + } + module("testcontainers-clickhouse") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/clickhouse/%s".formatted(version), "org.testcontainers.clickhouse") + } + module("testcontainers-couchbase") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/couchbase/%s".formatted(version), "org.testcontainers.couchbase") + } + module("testcontainers-elasticsearch") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/elasticsearch/%s".formatted(version), "org.testcontainers.elasticsearch") + } + module("testcontainers-grafana") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/grafana/%s".formatted(version), "org.testcontainers.grafana") + } + module("testcontainers-jdbc") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/jdbc/%s".formatted(version)) + } + module("testcontainers-junit-jupiter") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/junit-jupiter/%s".formatted(version), "org.testcontainers.junit.jupiter") + } + module("testcontainers-kafka") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/kafka/%s".formatted(version), "org.testcontainers.kafka") + } + module("testcontainers-mariadb") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/mariadb/%s".formatted(version)) + } + module("testcontainers-mongodb") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/mongodb/%s".formatted(version)) + } + module("testcontainers-mssqlserver") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/mssqlserver/%s".formatted(version)) + } + module("testcontainers-mysql") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/mysql/%s".formatted(version)) + } + module("testcontainers-neo4j") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/neo4j/%s".formatted(version)) + } + module("testcontainers-oracle-xe") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/oracle-xe/%s".formatted(version)) + } + module("testcontainers-oracle-free") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/oracle-free/%s".formatted(version), "org.testcontainers.oracle") + } + module("testcontainers-postgresql") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/postgresql/%s".formatted(version)) + } + module("testcontainers-pulsar") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/pulsar/%s".formatted(version)) + } + module("testcontainers-rabbitmq") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/rabbitmq/%s".formatted(version)) + } + module("testcontainers-redpanda") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/redpanda/%s".formatted(version), "org.testcontainers.redpanda") + } + module("testcontainers-r2dbc") { + javadoc(version -> "https://javadoc.io/doc/org.testcontainers/r2dbc/%s".formatted(version), "org.testcontainers.r2dbc") + } + } } library("Testcontainers Redis Module", "2.2.4") { @@ -2684,9 +2775,13 @@ bom { } links { site("https://www.thymeleaf.org") - javadoc("thymeleaf", version -> "https://www.thymeleaf.org/apidocs/thymeleaf/%s".formatted(version), "org.thymeleaf") - javadoc("thymeleaf-spring6", version -> "https://www.thymeleaf.org/apidocs/thymeleaf-spring6/%s".formatted(version), "org.thymeleaf.spring6") releaseNotes("https://github.com/thymeleaf/thymeleaf/releases/tag/thymeleaf-{version}") + module("thymeleaf") { + javadoc(version -> "https://www.thymeleaf.org/apidocs/thymeleaf/%s".formatted(version), "org.thymeleaf") + } + module("thymeleaf-spring6") { + javadoc(version -> "https://www.thymeleaf.org/apidocs/thymeleaf-spring6/%s".formatted(version), "org.thymeleaf.spring6") + } } } library("Thymeleaf Extras Data Attribute", "2.0.1") {