Add external links to `spring-boot-dependencies`
Update the BOM `Library` model to support external links that we can use in documentation and the release process. An additional `checkLinks` task has also been added to verify returned HTTP status codes. Closes gh-39779 Co-authored-by: Andy Wilkinson <andy.wilkinson@broadcom.com>
This commit is contained in:
parent
e44ec27fd6
commit
75c7bed6c6
|
|
@ -47,6 +47,7 @@ dependencies {
|
||||||
implementation("commons-codec:commons-codec:${versions.commonsCodec}")
|
implementation("commons-codec:commons-codec:${versions.commonsCodec}")
|
||||||
implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0")
|
implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0")
|
||||||
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}")
|
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}")
|
||||||
|
implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
|
||||||
implementation("org.apache.maven:maven-embedder:${versions.maven}")
|
implementation("org.apache.maven:maven-embedder:${versions.maven}")
|
||||||
implementation("org.asciidoctor:asciidoctor-gradle-jvm:3.3.2")
|
implementation("org.asciidoctor:asciidoctor-gradle-jvm:3.3.2")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}")
|
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}")
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
|
@ -66,11 +67,14 @@ import org.springframework.boot.build.bom.Library.VersionAlignment;
|
||||||
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
||||||
import org.springframework.boot.build.mavenplugin.MavenExec;
|
import org.springframework.boot.build.mavenplugin.MavenExec;
|
||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
import org.springframework.util.PropertyPlaceholderHelper;
|
||||||
|
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DSL extensions for {@link BomPlugin}.
|
* DSL extensions for {@link BomPlugin}.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
public class BomExtension {
|
public class BomExtension {
|
||||||
|
|
||||||
|
|
@ -119,7 +123,8 @@ public class BomExtension {
|
||||||
this.project, this.libraries, libraryHandler.groups)
|
this.project, this.libraries, libraryHandler.groups)
|
||||||
: null;
|
: null;
|
||||||
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
|
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
|
||||||
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment));
|
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment,
|
||||||
|
libraryHandler.links));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void effectiveBomArtifact() {
|
public void effectiveBomArtifact() {
|
||||||
|
|
@ -227,6 +232,8 @@ public class BomExtension {
|
||||||
|
|
||||||
private AlignWithVersionHandler alignWithVersion;
|
private AlignWithVersionHandler alignWithVersion;
|
||||||
|
|
||||||
|
private final Map<String, Function<LibraryVersion, String>> links = new HashMap<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LibraryHandler(String version) {
|
public LibraryHandler(String version) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
|
|
@ -263,6 +270,12 @@ public class BomExtension {
|
||||||
action.execute(this.alignWithVersion);
|
action.execute(this.alignWithVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void links(Action<LinksHandler> action) {
|
||||||
|
LinksHandler handler = new LinksHandler();
|
||||||
|
action.execute(handler);
|
||||||
|
this.links.putAll(handler.links);
|
||||||
|
}
|
||||||
|
|
||||||
public static class ProhibitedHandler {
|
public static class ProhibitedHandler {
|
||||||
|
|
||||||
private String reason;
|
private String reason;
|
||||||
|
|
@ -398,6 +411,67 @@ public class BomExtension {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class LinksHandler {
|
||||||
|
|
||||||
|
private final Map<String, Function<LibraryVersion, String>> links = new HashMap<>();
|
||||||
|
|
||||||
|
public void site(String linkTemplate) {
|
||||||
|
site(asFactory(linkTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void site(Function<LibraryVersion, String> linkFactory) {
|
||||||
|
add("site", linkFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void github(String linkTemplate) {
|
||||||
|
github(asFactory(linkTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void github(Function<LibraryVersion, String> linkFactory) {
|
||||||
|
add("github", linkFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void javadoc(String linkTemplate) {
|
||||||
|
javadoc(asFactory(linkTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void javadoc(Function<LibraryVersion, String> linkFactory) {
|
||||||
|
add("javadoc", linkFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reference(String linkTemplate) {
|
||||||
|
reference(asFactory(linkTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reference(Function<LibraryVersion, String> linkFactory) {
|
||||||
|
add("reference", linkFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseNotes(String linkTemplate) {
|
||||||
|
releaseNotes(asFactory(linkTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseNotes(Function<LibraryVersion, String> linkFactory) {
|
||||||
|
add("releaseNotes", linkFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String name, String linkTemplate) {
|
||||||
|
add(name, asFactory(linkTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String name, Function<LibraryVersion, String> linkFactory) {
|
||||||
|
this.links.put(name, linkFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<LibraryVersion, String> asFactory(String linkTemplate) {
|
||||||
|
return (version) -> {
|
||||||
|
PlaceholderResolver resolver = (name) -> "version".equals(name) ? version.toString() : null;
|
||||||
|
return new PropertyPlaceholderHelper("{", "}").replacePlaceholders(linkTemplate, resolver);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class UpgradeHandler {
|
public static class UpgradeHandler {
|
||||||
|
|
||||||
private UpgradePolicy upgradePolicy;
|
private UpgradePolicy upgradePolicy;
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ public class BomPlugin implements Plugin<Project> {
|
||||||
project.getTasks().named("check").configure((check) -> check.dependsOn(checkBom));
|
project.getTasks().named("check").configure((check) -> check.dependsOn(checkBom));
|
||||||
project.getTasks().create("bomrUpgrade", UpgradeBom.class, bom);
|
project.getTasks().create("bomrUpgrade", UpgradeBom.class, bom);
|
||||||
project.getTasks().create("moveToSnapshots", MoveToSnapshots.class, bom);
|
project.getTasks().create("moveToSnapshots", MoveToSnapshots.class, bom);
|
||||||
|
project.getTasks().register("checkLinks", CheckLinks.class, bom);
|
||||||
new PublishingCustomizer(project, bom).customize();
|
new PublishingCustomizer(project, bom).customize();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createApiEnforcedConfiguration(Project project) {
|
private void createApiEnforcedConfiguration(Project project) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024-2024 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.bom;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
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.tasks.TaskAction;
|
||||||
|
import org.gradle.internal.impldep.org.apache.http.client.config.CookieSpecs;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.http.client.ClientHttpResponse;
|
||||||
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||||
|
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task to check that links are working.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class CheckLinks extends DefaultTask {
|
||||||
|
|
||||||
|
private final BomExtension bom;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CheckLinks(BomExtension bom) {
|
||||||
|
this.bom = bom;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
void releaseNotes() {
|
||||||
|
RequestConfig config = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
|
||||||
|
CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(config).build();
|
||||||
|
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||||
|
RestTemplate restTemplate = new RestTemplate(requestFactory);
|
||||||
|
restTemplate.setErrorHandler(new IgnoringErrorHandler());
|
||||||
|
for (Library library : this.bom.getLibraries()) {
|
||||||
|
library.getLinks().forEach((name, link) -> {
|
||||||
|
URI uri;
|
||||||
|
try {
|
||||||
|
uri = new URI(link);
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.HEAD, null, String.class);
|
||||||
|
System.out.println("[%3d] %s - %s (%s)".formatted(response.getStatusCode().value(),
|
||||||
|
library.getName(), name, uri));
|
||||||
|
}
|
||||||
|
catch (URISyntaxException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class IgnoringErrorHandler extends DefaultResponseErrorHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError(ClientHttpResponse response) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.boot.build.bom;
|
package org.springframework.boot.build.bom;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
@ -24,6 +25,8 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||||
import org.apache.maven.artifact.versioning.VersionRange;
|
import org.apache.maven.artifact.versioning.VersionRange;
|
||||||
|
|
@ -59,6 +62,8 @@ public class Library {
|
||||||
|
|
||||||
private final VersionAlignment versionAlignment;
|
private final VersionAlignment versionAlignment;
|
||||||
|
|
||||||
|
private final Map<String, Function<LibraryVersion, String>> links;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@code Library} with the given {@code name}, {@code version}, and
|
* Create a new {@code Library} with the given {@code name}, {@code version}, and
|
||||||
* {@code groups}.
|
* {@code groups}.
|
||||||
|
|
@ -70,9 +75,11 @@ public class Library {
|
||||||
* @param prohibitedVersions version of the library that are prohibited
|
* @param prohibitedVersions version of the library that are prohibited
|
||||||
* @param considerSnapshots whether to consider snapshots
|
* @param considerSnapshots whether to consider snapshots
|
||||||
* @param versionAlignment version alignment, if any, for the library
|
* @param versionAlignment version alignment, if any, for the library
|
||||||
|
* @param links a list of HTTP links relevant to the library
|
||||||
*/
|
*/
|
||||||
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
|
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
|
||||||
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots, VersionAlignment versionAlignment) {
|
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots, VersionAlignment versionAlignment,
|
||||||
|
Map<String, Function<LibraryVersion, String>> links) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.calendarName = (calendarName != null) ? calendarName : name;
|
this.calendarName = (calendarName != null) ? calendarName : name;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
|
|
@ -82,6 +89,7 @@ public class Library {
|
||||||
this.prohibitedVersions = prohibitedVersions;
|
this.prohibitedVersions = prohibitedVersions;
|
||||||
this.considerSnapshots = considerSnapshots;
|
this.considerSnapshots = considerSnapshots;
|
||||||
this.versionAlignment = versionAlignment;
|
this.versionAlignment = versionAlignment;
|
||||||
|
this.links = Collections.unmodifiableMap(links);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
@ -116,6 +124,12 @@ public class Library {
|
||||||
return this.versionAlignment;
|
return this.versionAlignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getLinks() {
|
||||||
|
Map<String, String> links = new TreeMap<>();
|
||||||
|
this.links.forEach((name, linkFactory) -> links.put(name, linkFactory.apply(this.version)));
|
||||||
|
return Collections.unmodifiableMap(links);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A version or range of versions that are prohibited from being used in a bom.
|
* A version or range of versions that are prohibited from being used in a bom.
|
||||||
*/
|
*/
|
||||||
|
|
@ -184,6 +198,44 @@ public class Library {
|
||||||
return this.version;
|
return this.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int[] componentInts() {
|
||||||
|
return Arrays.stream(parts()).mapToInt(Integer::parseInt).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String major() {
|
||||||
|
return parts()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String minor() {
|
||||||
|
return parts()[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String patch() {
|
||||||
|
return parts()[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.version.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(String separator) {
|
||||||
|
return this.version.toString().replace(".", separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String forAntora() {
|
||||||
|
String[] parts = parts();
|
||||||
|
String result = parts[0] + "." + parts[1];
|
||||||
|
if (toString().endsWith("SNAPSHOT")) {
|
||||||
|
result += "-SNAPSHOT";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] parts() {
|
||||||
|
return toString().split("[\\.-]");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -53,7 +54,7 @@ class UpgradeApplicatorTests {
|
||||||
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
|
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
|
||||||
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
|
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
|
||||||
.apply(new Upgrade(new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")),
|
.apply(new Upgrade(new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")),
|
||||||
null, null, false, null), DependencyVersion.parse("5.16")));
|
null, null, false, null, Collections.emptyMap()), DependencyVersion.parse("5.16")));
|
||||||
String bomContents = Files.readString(bom.toPath());
|
String bomContents = Files.readString(bom.toPath());
|
||||||
assertThat(bomContents).hasSize(originalContents.length() - 3);
|
assertThat(bomContents).hasSize(originalContents.length() - 3);
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +67,7 @@ class UpgradeApplicatorTests {
|
||||||
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
|
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
|
||||||
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
|
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
|
||||||
.apply(new Upgrade(new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null,
|
.apply(new Upgrade(new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null,
|
||||||
null, false, null), DependencyVersion.parse("1.4")));
|
null, false, null, Collections.emptyMap()), DependencyVersion.parse("1.4")));
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
try (InputStream in = new FileInputStream(gradleProperties)) {
|
try (InputStream in = new FileInputStream(gradleProperties)) {
|
||||||
properties.load(in);
|
properties.load(in);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue