Prepare buildSrc for migration to Antora

Replace `AsciidoctorConventions` with `AntoraConventions` in
preparation for the migration to Antora.

See gh-33766
This commit is contained in:
Phillip Webb 2024-03-19 22:29:48 -07:00
parent d18f60e9af
commit 8d64e99714
22 changed files with 1187 additions and 228 deletions

View File

@ -17,7 +17,7 @@ def versions = [:]
new File(projectDir.parentFile, "gradle.properties").withInputStream {
def properties = new Properties()
properties.load(it)
["assertj", "commonsCodec", "hamcrest", "junitJupiter", "kotlin", "maven"].each {
["assertj", "commonsCodec", "hamcrest", "junitJupiter", "kotlin", "maven", "snakeYaml"].each {
versions[it] = properties[it + "Version"]
}
}
@ -42,20 +42,23 @@ dependencies {
implementation(platform("org.springframework:spring-framework-bom:${versions.springFramework}"))
implementation("com.diffplug.gradle:goomph:3.37.2")
implementation("com.fasterxml.jackson.core:jackson-databind:${versions.jackson}")
implementation("com.github.node-gradle:gradle-node-plugin:3.5.1")
implementation("com.gradle:gradle-enterprise-gradle-plugin:3.12.1")
implementation("com.tngtech.archunit:archunit:1.0.0")
implementation("commons-codec:commons-codec:${versions.commonsCodec}")
implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0")
implementation("io.spring.gradle.antora:spring-antora-plugin:0.0.1")
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}")
implementation("io.spring.nohttp:nohttp-gradle:0.0.11")
implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
implementation("org.apache.maven:maven-embedder:${versions.maven}")
implementation("org.asciidoctor:asciidoctor-gradle-jvm:3.3.2")
implementation("org.antora:gradle-antora-plugin:1.0.0")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}")
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${versions.kotlin}")
implementation("org.springframework:spring-context")
implementation("org.springframework:spring-core")
implementation("org.springframework:spring-web")
implementation("io.spring.nohttp:nohttp-gradle:0.0.11")
implementation("org.yaml:snakeyaml:${versions.snakeYaml}")
testImplementation("org.assertj:assertj-core:${versions.assertj}")
testImplementation("org.hamcrest:hamcrest:${versions.hamcrest}")

View File

@ -0,0 +1,155 @@
/*
* Copyright 2023-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;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.github.gradle.node.NodeExtension;
import io.spring.gradle.antora.GenerateAntoraYmlPlugin;
import io.spring.gradle.antora.GenerateAntoraYmlTask;
import org.antora.gradle.AntoraExtension;
import org.antora.gradle.AntoraPlugin;
import org.antora.gradle.AntoraTask;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskContainer;
import org.springframework.boot.build.antora.AntoraAsciidocAttributes;
import org.springframework.boot.build.antora.Extensions;
import org.springframework.boot.build.antora.GenerateAntoraPlaybook;
import org.springframework.boot.build.bom.BomExtension;
import org.springframework.boot.build.constraints.ExtractVersionConstraints;
import org.springframework.util.Assert;
/**
* Conventions that are applied in the presence of the {@link AntoraPlugin} and
* {@link GenerateAntoraYmlPlugin}.
*
* @author Phillip Webb
*/
public class AntoraConventions {
private static final String DEPENDENCIES_PATH = ":spring-boot-project:spring-boot-dependencies";
private static final String ANTORA_VERSION = "3.2.0-alpha.4";
private static final String ANTORA_SOURCE_DIR = "src/docs/antora";
private static final List<String> NAV_FILES = List.of("nav.adoc", "local-nav.adoc");
void apply(Project project) {
project.getPlugins().withType(AntoraPlugin.class, (antoraPlugin) -> apply(project, antoraPlugin));
}
private void apply(Project project, AntoraPlugin antoraPlugin) {
ExtractVersionConstraints dependencyVersionsTask = addDependencyVersionsTask(project);
project.getPlugins().apply(GenerateAntoraYmlPlugin.class);
TaskContainer tasks = project.getTasks();
GenerateAntoraPlaybook generateAntoraPlaybookTask = tasks.create("generateAntoraPlaybook",
GenerateAntoraPlaybook.class);
tasks.withType(GenerateAntoraYmlTask.class, (generateAntoraYmlTask) -> configureGenerateAntoraYmlTask(project,
generateAntoraYmlTask, dependencyVersionsTask));
tasks.withType(AntoraTask.class,
(antoraTask) -> configureAntoraTask(project, antoraTask, generateAntoraPlaybookTask));
project.getExtensions().configure(AntoraExtension.class, (antoraExtension) -> {
RegularFileProperty outputFile = generateAntoraPlaybookTask.getOutputFile();
configureAntoraExtension(project, antoraExtension, outputFile);
});
project.getExtensions()
.configure(NodeExtension.class, (nodeExtension) -> configureNodeExtension(project, nodeExtension));
}
private ExtractVersionConstraints addDependencyVersionsTask(Project project) {
return project.getTasks()
.create("dependencyVersions", ExtractVersionConstraints.class,
(task) -> task.enforcedPlatform(DEPENDENCIES_PATH));
}
private void configureGenerateAntoraYmlTask(Project project, GenerateAntoraYmlTask generateAntoraYmlTask,
ExtractVersionConstraints dependencyVersionsTask) {
generateAntoraYmlTask.getOutputs().doNotCacheIf("getAsciidocAttributes() changes output", (task) -> true);
generateAntoraYmlTask.dependsOn(dependencyVersionsTask);
generateAntoraYmlTask.setProperty("componentName", "spring-boot");
generateAntoraYmlTask.setProperty("outputFile",
new File(project.getBuildDir(), "generated/docs/antora-yml/antora.yml"));
generateAntoraYmlTask.setProperty("yml", getDefaultYml(project));
generateAntoraYmlTask.doFirst((task) -> generateAntoraYmlTask.getAsciidocAttributes()
.putAll(project.provider(() -> getAsciidocAttributes(project, dependencyVersionsTask))));
}
private Map<String, ?> getDefaultYml(Project project) {
String navFile = null;
for (String candidate : NAV_FILES) {
if (project.file(ANTORA_SOURCE_DIR + "/" + candidate).exists()) {
Assert.state(navFile == null, "Multiple nav files found");
navFile = candidate;
}
}
Map<String, Object> defaultYml = new LinkedHashMap<>();
defaultYml.put("title", "Spring Boot");
if (navFile != null) {
defaultYml.put("nav", List.of(navFile));
}
return defaultYml;
}
private Map<String, String> getAsciidocAttributes(Project project,
ExtractVersionConstraints dependencyVersionsTask) {
BomExtension bom = (BomExtension) project.project(DEPENDENCIES_PATH).getExtensions().getByName("bom");
Map<String, String> dependencyVersions = dependencyVersionsTask.getVersionConstraints();
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes(project, bom, dependencyVersions);
return attributes.get();
}
private void configureAntoraTask(Project project, AntoraTask antoraTask,
GenerateAntoraPlaybook generateAntoraPlaybookTask) {
antoraTask.setGroup("Documentation");
antoraTask.getDependsOn().add(generateAntoraPlaybookTask);
project.getPlugins()
.withType(JavaBasePlugin.class,
(javaBasePlugin) -> project.getTasks()
.getByName(JavaBasePlugin.CHECK_TASK_NAME)
.dependsOn(antoraTask));
}
private void configureAntoraExtension(Project project, AntoraExtension antoraExtension,
Provider<RegularFile> playbook) {
antoraExtension.getVersion().convention(ANTORA_VERSION);
antoraExtension.getPackages().convention(Extensions.packages());
antoraExtension.getPlaybook().convention(playbook.map(RegularFile::getAsFile));
if (project.getGradle().getStartParameter().getLogLevel() != LogLevel.DEBUG) {
antoraExtension.getOptions().add("--quiet");
}
else {
antoraExtension.getOptions().addAll("--log-level", "all");
}
}
private void configureNodeExtension(Project project, NodeExtension nodeExtension) {
File buildDir = project.getBuildDir();
nodeExtension.getWorkDir().set(buildDir.toPath().resolve(".gradle/nodejs").toFile());
nodeExtension.getNpmWorkDir().set(buildDir.toPath().resolve(".gradle/npm").toFile());
}
}

View File

@ -1,159 +0,0 @@
/*
* Copyright 2012-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;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask;
import org.asciidoctor.gradle.jvm.AsciidoctorJExtension;
import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin;
import org.asciidoctor.gradle.jvm.AsciidoctorTask;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.Sync;
import org.springframework.boot.build.artifacts.ArtifactRelease;
import org.springframework.util.StringUtils;
/**
* Conventions that are applied in the presence of the {@link AsciidoctorJPlugin}. When
* the plugin is applied:
*
* <ul>
* <li>All warnings are made fatal.
* <li>The version of AsciidoctorJ is upgraded to 2.4.3.
* <li>An {@code asciidoctorExtensions} configuration is created.
* <li>For each {@link AsciidoctorTask} (HTML only):
* <ul>
* <li>A task is created to sync the documentation resources to its output directory.
* <li>{@code doctype} {@link AsciidoctorTask#options(Map) option} is configured.
* <li>The {@code backend} is configured.
* </ul>
* <li>For each {@link AbstractAsciidoctorTask} (HTML and PDF):
* <ul>
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured to enable
* warnings for references to missing attributes, the GitHub tag, the Artifactory repo for
* the current version, etc.
* <li>{@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()}
* is enabled.
* <li>{@code asciidoctorExtensions} is added to the task's configurations.
* </ul>
* </ul>
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
class AsciidoctorConventions {
private static final String ASCIIDOCTORJ_VERSION = "2.4.3";
private static final String EXTENSIONS_CONFIGURATION_NAME = "asciidoctorExtensions";
void apply(Project project) {
project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> {
makeAllWarningsFatal(project);
upgradeAsciidoctorJVersion(project);
createAsciidoctorExtensionsConfiguration(project);
project.getTasks()
.withType(AbstractAsciidoctorTask.class,
(asciidoctorTask) -> configureAsciidoctorTask(project, asciidoctorTask));
});
}
private void makeAllWarningsFatal(Project project) {
project.getExtensions().getByType(AsciidoctorJExtension.class).fatalWarnings(".*");
}
private void upgradeAsciidoctorJVersion(Project project) {
project.getExtensions().getByType(AsciidoctorJExtension.class).setVersion(ASCIIDOCTORJ_VERSION);
}
private void createAsciidoctorExtensionsConfiguration(Project project) {
project.getConfigurations().create(EXTENSIONS_CONFIGURATION_NAME, (configuration) -> {
project.getConfigurations()
.matching((candidate) -> "dependencyManagement".equals(candidate.getName()))
.all(configuration::extendsFrom);
configuration.getDependencies()
.add(project.getDependencies()
.create("io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.5"));
configuration.getDependencies()
.add(project.getDependencies().create("org.asciidoctor:asciidoctorj-pdf:1.5.3"));
});
}
private void configureAsciidoctorTask(Project project, AbstractAsciidoctorTask asciidoctorTask) {
asciidoctorTask.configurations(EXTENSIONS_CONFIGURATION_NAME);
configureCommonAttributes(project, asciidoctorTask);
configureOptions(asciidoctorTask);
configureForkOptions(asciidoctorTask);
asciidoctorTask.baseDirFollowsSourceDir();
createSyncDocumentationSourceTask(project, asciidoctorTask);
if (asciidoctorTask instanceof AsciidoctorTask task) {
boolean pdf = task.getName().toLowerCase().contains("pdf");
String backend = (!pdf) ? "spring-html" : "spring-pdf";
task.outputOptions((outputOptions) -> outputOptions.backends(backend));
}
}
private void configureCommonAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) {
ArtifactRelease artifacts = ArtifactRelease.forProject(project);
Map<String, Object> attributes = new HashMap<>();
attributes.put("attribute-missing", "warn");
attributes.put("github-tag", determineGitHubTag(project));
attributes.put("artifact-release-type", artifacts.getType());
attributes.put("artifact-download-repo", artifacts.getDownloadRepo());
attributes.put("revnumber", null);
asciidoctorTask.attributes(attributes);
}
// See https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/597
private void configureForkOptions(AbstractAsciidoctorTask asciidoctorTask) {
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) {
asciidoctorTask.forkOptions((options) -> options.jvmArgs("--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED",
"--add-opens", "java.base/java.io=ALL-UNNAMED"));
}
}
private String determineGitHubTag(Project project) {
String version = "v" + project.getVersion();
return (version.endsWith("-SNAPSHOT")) ? "main" : version;
}
private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) {
asciidoctorTask.options(Collections.singletonMap("doctype", "book"));
}
private Sync createSyncDocumentationSourceTask(Project project, AbstractAsciidoctorTask asciidoctorTask) {
Sync syncDocumentationSource = project.getTasks()
.create("syncDocumentationSourceFor" + StringUtils.capitalize(asciidoctorTask.getName()), Sync.class);
File syncedSource = new File(project.getBuildDir(), "docs/src/" + asciidoctorTask.getName());
syncDocumentationSource.setDestinationDir(syncedSource);
syncDocumentationSource.from("src/docs/");
asciidoctorTask.dependsOn(syncDocumentationSource);
asciidoctorTask.getInputs()
.dir(syncedSource)
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("synced source");
asciidoctorTask.setSourceDir(project.relativePath(new File(syncedSource, "asciidoc/")));
return syncDocumentationSource;
}
}

View File

@ -16,7 +16,7 @@
package org.springframework.boot.build;
import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin;
import org.antora.gradle.AntoraPlugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaBasePlugin;
@ -32,8 +32,8 @@ import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
* When the {@link MavenPublishPlugin} is applied, the conventions in
* {@link MavenPublishingConventions} are applied.
*
* When the {@link AsciidoctorJPlugin} is applied, the conventions in
* {@link AsciidoctorConventions} are applied.
* When the {@link AntoraPlugin} is applied, the conventions in {@link AntoraConventions}
* are applied.
*
* @author Andy Wilkinson
* @author Christoph Dreis
@ -46,7 +46,7 @@ public class ConventionsPlugin implements Plugin<Project> {
new NoHttpConventions().apply(project);
new JavaConventions().apply(project);
new MavenPublishingConventions().apply(project);
new AsciidoctorConventions().apply(project);
new AntoraConventions().apply(project);
new KotlinConventions().apply(project);
new WarConventions().apply(project);
new EclipseConventions().apply(project);

View File

@ -0,0 +1,162 @@
/*
* Copyright 2012-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.antora;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
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.util.Assert;
/**
* Generates Asciidoctor attributes for use with Antora.
*
* @author Phillip Webb
*/
public class AntoraAsciidocAttributes {
private static final String DASH_SNAPSHOT = "-SNAPSHOT";
private final String version;
private final boolean latestVersion;
private final ArtifactRelease artifactRelease;
private final List<Library> libraries;
private final Map<String, String> dependencyVersions;
private final Map<String, ?> projectProperties;
public AntoraAsciidocAttributes(Project project, BomExtension dependencyBom,
Map<String, String> dependencyVersions) {
this.version = String.valueOf(project.getVersion());
this.latestVersion = Boolean.valueOf(String.valueOf(project.findProperty("latestVersion")));
this.artifactRelease = ArtifactRelease.forProject(project);
this.libraries = dependencyBom.getLibraries();
this.dependencyVersions = dependencyVersions;
this.projectProperties = project.getProperties();
}
AntoraAsciidocAttributes(String version, boolean latestVersion, List<Library> libraries,
Map<String, String> dependencyVersions, Map<String, ?> projectProperties) {
this.version = version;
this.latestVersion = latestVersion;
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<String, String> get() {
Map<String, String> attributes = new LinkedHashMap<>();
addGitHubAttributes(attributes);
addVersionAttributes(attributes);
addUrlArtifactRepository(attributes);
addUrlLibraryLinkAttributes(attributes);
addPropertyAttributes(attributes);
return attributes;
}
private void addGitHubAttributes(Map<String, String> attributes) {
attributes.put("github-repo", "spring-projects/spring-boot");
attributes.put("github-ref", determineGitHubRef());
}
private String determineGitHubRef() {
int snapshotIndex = this.version.lastIndexOf(DASH_SNAPSHOT);
if (snapshotIndex == -1) {
return "v" + this.version;
}
if (this.latestVersion) {
return "main";
}
String versionRoot = this.version.substring(0, snapshotIndex);
int lastDot = versionRoot.lastIndexOf('.');
return versionRoot.substring(0, lastDot) + ".x";
}
private void addVersionAttributes(Map<String, String> attributes) {
this.libraries.forEach((library) -> {
String name = "version-" + library.getLinkRootName();
String value = library.getVersion().toString();
attributes.put(name, value);
});
attributes.put("version-native-build-tools", (String) this.projectProperties.get("nativeBuildToolsVersion"));
attributes.put("version-graal", (String) this.projectProperties.get("graalVersion"));
addSpringDataDependencyVersion(attributes, "spring-data-commons");
addSpringDataDependencyVersion(attributes, "spring-data-couchbase");
addSpringDataDependencyVersion(attributes, "spring-data-elasticsearch");
addSpringDataDependencyVersion(attributes, "spring-data-jdbc");
addSpringDataDependencyVersion(attributes, "spring-data-jpa");
addSpringDataDependencyVersion(attributes, "spring-data-mongodb");
addSpringDataDependencyVersion(attributes, "spring-data-neo4j");
addSpringDataDependencyVersion(attributes, "spring-data-r2dbc");
addSpringDataDependencyVersion(attributes, "spring-data-rest", "spring-data-rest-core");
}
private void addSpringDataDependencyVersion(Map<String, String> attributes, String artifactId) {
addSpringDataDependencyVersion(attributes, artifactId, artifactId);
}
private void addSpringDataDependencyVersion(Map<String, String> attributes, String name, String artifactId) {
String version = this.dependencyVersions.get("org.springframework.data:" + artifactId);
Assert.notNull(version, () -> "No version found for Spring Data artificat " + artifactId);
attributes.put("version-" + name, version);
}
private void addUrlArtifactRepository(Map<String, String> attributes) {
attributes.put("url-artifact-repository", this.artifactRelease.getDownloadRepo());
}
private void addUrlLibraryLinkAttributes(Map<String, String> attributes) {
this.libraries.forEach((library) -> {
String prefix = "url-" + library.getLinkRootName() + "-";
library.getLinks().forEach((name, link) -> attributes.put(prefix + name, link));
});
}
private void addPropertyAttributes(Map<String, String> attributes) {
Properties properties = new Properties() {
@Override
public synchronized Object put(Object key, Object value) {
// Put directly because order is important for us
return attributes.put(key.toString(), value.toString());
}
};
try (InputStream in = getClass().getResourceAsStream("antora-asciidoc-attributes.properties")) {
properties.load(in);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}

View File

@ -0,0 +1,197 @@
/*
* Copyright 2012-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.antora;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* Antora and Asciidoc extensions used by Spring Boot.
*
* @author Phillip Webb
*/
public final class Extensions {
private static final String ROOT_COMPONENT_EXTENSION = "@springio/antora-extensions/root-component-extension";
private static final List<Extension> antora;
static {
List<Extension> extensions = new ArrayList<>();
extensions.add(new Extension("@springio/antora-extensions", "1.8.2", ROOT_COMPONENT_EXTENSION,
"@springio/antora-extensions/static-page-extension"));
extensions.add(new Extension("@springio/antora-xref-extension", "1.0.0-alpha.3"));
extensions.add(new Extension("@springio/antora-zip-contents-collector-extension", "1.0.0-alpha.2"));
antora = List.copyOf(extensions);
}
private static final List<Extension> asciidoc;
static {
List<Extension> extensions = new ArrayList<>();
extensions.add(new Extension("@asciidoctor/tabs", "1.0.0-beta.6"));
extensions
.add(new Extension("@springio/asciidoctor-extensions", "1.0.0-alpha.10", "@springio/asciidoctor-extensions",
"@springio/asciidoctor-extensions/configuration-properties-extension",
"@springio/asciidoctor-extensions/section-ids-extension"));
asciidoc = List.copyOf(extensions);
}
private static final Map<String, String> localOverrides = Collections.emptyMap();
private Extensions() {
}
public static Map<String, String> packages() {
Map<String, String> packages = new TreeMap<>();
antora.stream().forEach((extension) -> packages.put(extension.name(), extension.version()));
asciidoc.stream().forEach((extension) -> packages.put(extension.name(), extension.version()));
return Collections.unmodifiableMap(packages);
}
static List<Map<String, Object>> antora(Consumer<AntoraExtensionsConfiguration> extensions) {
AntoraExtensionsConfiguration result = new AntoraExtensionsConfiguration(
antora.stream().flatMap(Extension::names).sorted().toList());
extensions.accept(result);
return result.config();
}
static List<String> asciidoc() {
return asciidoc.stream().flatMap(Extension::names).sorted().toList();
}
private record Extension(String name, String version, String... includeNames) {
Stream<String> names() {
return (this.includeNames.length != 0) ? Arrays.stream(this.includeNames) : Stream.of(this.name);
}
}
static final class AntoraExtensionsConfiguration {
private Map<String, Map<String, Object>> extensions = new TreeMap<>();
private AntoraExtensionsConfiguration(List<String> names) {
names.forEach((name) -> this.extensions.put(name, null));
}
void xref(Consumer<Xref> xref) {
xref.accept(new Xref());
}
void zipContentsCollector(Consumer<ZipContentsCollector> zipContentsCollector) {
zipContentsCollector.accept(new ZipContentsCollector());
}
void rootComponent(Consumer<RootComponent> rootComponent) {
rootComponent.accept(new RootComponent());
}
List<Map<String, Object>> config() {
List<Map<String, Object>> config = new ArrayList<>();
Map<String, Map<String, Object>> orderedExtensions = new LinkedHashMap<>(this.extensions);
// The root component extension must be last
Map<String, Object> rootComponentConfig = orderedExtensions.remove(ROOT_COMPONENT_EXTENSION);
orderedExtensions.put(ROOT_COMPONENT_EXTENSION, rootComponentConfig);
orderedExtensions.forEach((name, customizations) -> {
Map<String, Object> extensionConfig = new LinkedHashMap<>();
extensionConfig.put("require", localOverrides.getOrDefault(name, name));
if (customizations != null) {
extensionConfig.putAll(customizations);
}
config.add(extensionConfig);
});
return List.copyOf(config);
}
abstract class Customizer {
private final String name;
Customizer(String name) {
this.name = name;
}
protected void customize(String key, Object value) {
AntoraExtensionsConfiguration.this.extensions.computeIfAbsent(this.name, (name) -> new TreeMap<>())
.put(key, value);
}
}
class Xref extends Customizer {
Xref() {
super("@springio/antora-xref-extension");
}
void stub(List<String> stub) {
if (stub != null && !stub.isEmpty()) {
customize("stub", stub);
}
}
}
class ZipContentsCollector extends Customizer {
ZipContentsCollector() {
super("@springio/antora-zip-contents-collector-extension");
}
void versionFile(String versionFile) {
customize("version_file", versionFile);
}
void locations(Path... locations) {
locations(Arrays.stream(locations).map(Path::toString).toList());
}
private void locations(List<String> locations) {
customize("locations", locations);
}
void alwaysInclude(Map<String, String> alwaysInclude) {
if (alwaysInclude != null && !alwaysInclude.isEmpty()) {
customize("always_include", List.of(new TreeMap<>(alwaysInclude)));
}
}
}
class RootComponent extends Customizer {
RootComponent() {
super(ROOT_COMPONENT_EXTENSION);
}
void name(String name) {
customize("root_component_name", name);
}
}
}
}

View File

@ -0,0 +1,209 @@
/*
* Copyright 2012-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.antora;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
/**
* Task to generate a local Antora playbook.
*
* @author Phillip Webb
*/
public abstract class GenerateAntoraPlaybook extends DefaultTask {
private static final String ANTORA_SOURCE_DIR = "src/docs/antora";
private static final String GENERATED_DOCS = "build/generated/docs/";
@OutputFile
public abstract RegularFileProperty getOutputFile();
@Input
public abstract Property<String> getContentSourceConfiguration();
@Input
@Optional
public abstract ListProperty<String> getXrefStubs();
@Input
@Optional
public abstract MapProperty<String, String> getAlwaysInclude();
public GenerateAntoraPlaybook() {
setGroup("Documentation");
setDescription("Generates an Antora playbook.yml file for local use");
getOutputFile().convention(getProject().getLayout()
.getBuildDirectory()
.file("generated/docs/antora-playbook/antora-playbook.yml"));
getContentSourceConfiguration().convention("antoraContent");
}
@TaskAction
public void writePlaybookYml() throws IOException {
File file = getOutputFile().get().getAsFile();
file.getParentFile().mkdirs();
try (FileWriter out = new FileWriter(file)) {
createYaml().dump(getData(), out);
}
}
@Input
final Map<String, Object> getData() throws IOException {
Map<String, Object> data = loadPlaybookTemplate();
addExtensions(data);
addSources(data);
addDir(data);
return data;
}
@SuppressWarnings("unchecked")
private Map<String, Object> loadPlaybookTemplate() throws IOException {
try (InputStream resource = getClass().getResourceAsStream("antora-playbook-template.yml")) {
return createYaml().loadAs(resource, LinkedHashMap.class);
}
}
@SuppressWarnings("unchecked")
private void addExtensions(Map<String, Object> data) {
Map<String, Object> antora = (Map<String, Object>) data.get("antora");
antora.put("extensions", Extensions.antora((extensions) -> {
extensions.xref((xref) -> xref.stub(getXrefStubs().getOrElse(Collections.emptyList())));
extensions.zipContentsCollector((zipContentsCollector) -> {
zipContentsCollector.versionFile("gradle.properties");
String locationName = getProject().getName() + "-${version}-${name}-${classifier}.zip";
Path antoraContent = getRelativeProjectPath()
.resolve(GENERATED_DOCS + "antora-content/" + locationName);
Path antoraDepenencies = getRelativeProjectPath()
.resolve(GENERATED_DOCS + "antora-dependencies-content/" + locationName);
zipContentsCollector.locations(antoraContent, antoraDepenencies);
zipContentsCollector.alwaysInclude(getAlwaysInclude().getOrNull());
});
extensions.rootComponent((rootComponent) -> rootComponent.name("spring-boot"));
}));
Map<String, Object> asciidoc = (Map<String, Object>) data.get("asciidoc");
asciidoc.put("extensions", Extensions.asciidoc());
}
private void addSources(Map<String, Object> data) {
List<Map<String, Object>> contentSources = getList(data, "content.sources");
contentSources.add(createContentSource());
}
private Map<String, Object> createContentSource() {
Map<String, Object> source = new LinkedHashMap<>();
Path playbookPath = getOutputFile().get().getAsFile().toPath().getParent();
Path antoraSrc = getProjectPath(getProject()).resolve(ANTORA_SOURCE_DIR);
StringBuilder url = new StringBuilder(".");
relativizeFromRootProject(playbookPath).normalize().forEach((path) -> url.append("/.."));
source.put("url", url.toString());
source.put("branches", "HEAD");
source.put("version", getProject().getVersion().toString());
Set<String> startPaths = new LinkedHashSet<>();
addAntoraContentStartPaths(startPaths);
startPaths.add(relativizeFromRootProject(antoraSrc).toString());
source.put("start_paths", startPaths.stream().toList());
return source;
}
private void addAntoraContentStartPaths(Set<String> startPaths) {
Configuration configuration = getProject().getConfigurations().findByName("antoraContent");
if (configuration != null) {
for (ProjectDependency dependency : configuration.getAllDependencies().withType(ProjectDependency.class)) {
Path path = dependency.getDependencyProject().getProjectDir().toPath();
startPaths.add(relativizeFromRootProject(path).resolve(ANTORA_SOURCE_DIR).toString());
}
}
}
private void addDir(Map<String, Object> data) {
Path playbookDir = toRealPath(getOutputFile().get().getAsFile().toPath()).getParent();
Path outputDir = toRealPath(getProject().getBuildDir().toPath().resolve("site"));
data.put("output", Map.of("dir", "./" + playbookDir.relativize(outputDir).toString()));
}
@SuppressWarnings("unchecked")
private <T> List<T> getList(Map<String, Object> data, String location) {
return (List<T>) get(data, location);
}
@SuppressWarnings("unchecked")
private Object get(Map<String, Object> data, String location) {
Object result = data;
String[] keys = location.split("\\.");
for (String key : keys) {
result = ((Map<String, Object>) result).get(key);
}
return result;
}
private Yaml createYaml() {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
return new Yaml(options);
}
private Path getRelativeProjectPath() {
return relativizeFromRootProject(getProjectPath(getProject()));
}
private Path relativizeFromRootProject(Path subPath) {
Path rootProjectPath = getProjectPath(getProject().getRootProject());
return rootProjectPath.relativize(subPath).normalize();
}
private Path getProjectPath(Project project) {
return toRealPath(project.getProjectDir().toPath());
}
private Path toRealPath(Path path) {
try {
return Files.exists(path) ? path.toRealPath() : path;
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-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.
@ -26,24 +26,18 @@ import org.gradle.api.Project;
*/
public final class ArtifactRelease {
private static final String SNAPSHOT = "snapshot";
private static final String MILESTONE = "milestone";
private static final String RELEASE = "release";
private static final String SPRING_REPO = "https://repo.spring.io/%s";
private static final String MAVEN_REPO = "https://repo.maven.apache.org/maven2";
private final String type;
private final Type type;
private ArtifactRelease(String type) {
private ArtifactRelease(Type type) {
this.type = type;
}
public String getType() {
return this.type;
return this.type.toString().toLowerCase();
}
public String getDownloadRepo() {
@ -51,24 +45,34 @@ public final class ArtifactRelease {
}
public boolean isRelease() {
return RELEASE.equals(this.type);
return this.type == Type.RELEASE;
}
public static ArtifactRelease forProject(Project project) {
return new ArtifactRelease(determineReleaseType(project));
return forVersion(project.getVersion().toString());
}
private static String determineReleaseType(Project project) {
String version = project.getVersion().toString();
int modifierIndex = version.lastIndexOf('-');
if (modifierIndex == -1) {
return RELEASE;
public static ArtifactRelease forVersion(String version) {
return new ArtifactRelease(Type.forVersion(version));
}
enum Type {
SNAPSHOT, MILESTONE, RELEASE;
static Type forVersion(String version) {
int modifierIndex = version.lastIndexOf('-');
if (modifierIndex == -1) {
return RELEASE;
}
String type = version.substring(modifierIndex + 1);
if (type.startsWith("M") || type.startsWith("RC")) {
return MILESTONE;
}
return SNAPSHOT;
}
String type = version.substring(modifierIndex + 1);
if (type.startsWith("M") || type.startsWith("RC")) {
return MILESTONE;
}
return SNAPSHOT;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-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.
@ -89,9 +89,9 @@ public class DocumentAutoConfigurationClasses extends DefaultTask {
for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses.classes) {
writer.println();
writer.printf("| {spring-boot-code}/spring-boot-project/%s/src/main/java/%s.java[`%s`]%n",
writer.printf("| {code-spring-boot}/spring-boot-project/%s/src/main/java/%s.java[`%s`]%n",
autoConfigurationClasses.module, autoConfigurationClass.path, autoConfigurationClass.name);
writer.printf("| {spring-boot-api}/%s.html[javadoc]%n", autoConfigurationClass.path);
writer.printf("| xref:api:java/%s.html[javadoc]%n", autoConfigurationClass.path);
}
writer.println("|===");

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-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.
@ -29,6 +29,7 @@ import org.gradle.api.DefaultTask;
import org.gradle.api.Task;
import org.gradle.api.artifacts.ComponentMetadataDetails;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencyConstraint;
import org.gradle.api.artifacts.DependencyConstraintMetadata;
import org.gradle.api.artifacts.dsl.DependencyHandler;
@ -64,10 +65,9 @@ public class ExtractVersionConstraints extends DefaultTask {
}
public void enforcedPlatform(String projectPath) {
this.configuration.getDependencies()
.add(getProject().getDependencies()
.enforcedPlatform(
getProject().getDependencies().project(Collections.singletonMap("path", projectPath))));
Dependency project = getProject().getDependencies().project(Map.of("path", projectPath));
Dependency dependency = getProject().getDependencies().enforcedPlatform(project);
this.configuration.getDependencies().add(dependency);
this.projectPaths.add(projectPath);
}
@ -104,9 +104,8 @@ public class ExtractVersionConstraints extends DefaultTask {
}
private void extractVersionProperties(String projectPath) {
Object bom = getProject().project(projectPath).getExtensions().getByName("bom");
BomExtension bomExtension = (BomExtension) bom;
for (Library lib : bomExtension.getLibraries()) {
BomExtension bom = (BomExtension) getProject().project(projectPath).getExtensions().getByName("bom");
for (Library lib : bom.getLibraries()) {
String versionProperty = lib.getVersionProperty();
if (versionProperty != null) {
this.versionProperties.add(new VersionProperty(lib.getName(), versionProperty));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-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.
@ -102,11 +102,7 @@ class Snippets {
private void writeAsciidoc(Path outputDirectory, Snippet snippet, Asciidoc asciidoc) throws IOException {
String[] parts = (snippet.getAnchor()).split("\\.");
Path path = outputDirectory;
for (int i = 0; i < parts.length; i++) {
String name = (i < parts.length - 1) ? parts[i] : parts[i] + ".adoc";
path = path.resolve(name);
}
Path path = outputDirectory.resolve(parts[parts.length - 1] + ".adoc");
createDirectory(path.getParent());
Files.deleteIfExists(path);
try (OutputStream outputStream = Files.newOutputStream(path)) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-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.
@ -50,7 +50,7 @@ public class DocumentDevtoolsPropertyDefaults extends DefaultTask {
this.outputFile = getProject().getObjects().fileProperty();
this.outputFile.convention(getProject().getLayout()
.getBuildDirectory()
.file("docs/generated/using/devtools-property-defaults.adoc"));
.file("generated/docs/using/devtools-property-defaults.adoc"));
Map<String, String> dependency = new HashMap<>();
dependency.put("path", ":spring-boot-project:spring-boot-devtools");
dependency.put("configuration", "propertyDefaults");

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-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.
@ -92,7 +92,7 @@ public class DocumentPluginGoals extends DefaultTask {
writer.println("| Goal | Description");
writer.println();
for (Mojo mojo : plugin.getMojos()) {
writer.printf("| <<%s,%s:%s>>%n", goalSectionId(mojo), plugin.getGoalPrefix(), mojo.getGoal());
writer.printf("| xref:%s[%s:%s]%n", goalSectionId(mojo, false), plugin.getGoalPrefix(), mojo.getGoal());
writer.printf("| %s%n", mojo.getDescription());
writer.println();
}
@ -102,11 +102,9 @@ public class DocumentPluginGoals extends DefaultTask {
private void documentMojo(Plugin plugin, Mojo mojo) throws IOException {
try (PrintWriter writer = new PrintWriter(new FileWriter(new File(this.outputDir, mojo.getGoal() + ".adoc")))) {
String sectionId = goalSectionId(mojo);
writer.println();
writer.println();
String sectionId = goalSectionId(mojo, true);
writer.printf("[[%s]]%n", sectionId);
writer.printf("= `%s:%s`%n", plugin.getGoalPrefix(), mojo.getGoal());
writer.printf("= `%s:%s`%n%n", plugin.getGoalPrefix(), mojo.getGoal());
writer.printf("`%s:%s:%s`%n", plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion());
writer.println();
writer.println(mojo.getDescription());
@ -114,37 +112,43 @@ public class DocumentPluginGoals extends DefaultTask {
List<Parameter> requiredParameters = parameters.stream().filter(Parameter::isRequired).toList();
String detailsSectionId = sectionId + ".parameter-details";
if (!requiredParameters.isEmpty()) {
writer.println();
writer.println();
writer.println();
writer.printf("[[%s.required-parameters]]%n", sectionId);
writer.println("== Required parameters");
writer.println();
writeParametersTable(writer, detailsSectionId, requiredParameters);
}
List<Parameter> optionalParameters = parameters.stream()
.filter((parameter) -> !parameter.isRequired())
.toList();
if (!optionalParameters.isEmpty()) {
writer.println();
writer.println();
writer.println();
writer.printf("[[%s.optional-parameters]]%n", sectionId);
writer.println("== Optional parameters");
writer.println();
writeParametersTable(writer, detailsSectionId, optionalParameters);
}
writer.println();
writer.println();
writer.println();
writer.printf("[[%s]]%n", detailsSectionId);
writer.println("== Parameter details");
writer.println();
writeParameterDetails(writer, parameters, detailsSectionId);
}
}
private String goalSectionId(Mojo mojo) {
private String goalSectionId(Mojo mojo, boolean innerReference) {
String goalSection = this.goalSections.get(mojo.getGoal());
if (goalSection == null) {
throw new IllegalStateException("Goal '" + mojo.getGoal() + "' has not be assigned to a section");
}
String sectionId = goalSection + "." + mojo.getGoal() + "-goal";
return sectionId;
return (!innerReference) ? goalSection + "#" + sectionId : sectionId;
}
private void writeParametersTable(PrintWriter writer, String detailsSectionId, List<Parameter> parameters) {
@ -236,10 +240,10 @@ public class DocumentPluginGoals extends DefaultTask {
private String typeNameToJavadocLink(String shortName, String name) {
if (name.startsWith("org.springframework.boot.maven")) {
return "{spring-boot-docs}/maven-plugin/api/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]";
return "xref:maven-plugin:api/java/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]";
}
if (name.startsWith("org.springframework.boot")) {
return "{spring-boot-docs}/api/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]";
return "xref:api:java/" + typeNameToJavadocPath(name) + ".html[" + shortName + "]";
}
return shortName;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-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.
@ -158,7 +158,7 @@ public class MavenPluginPlugin implements Plugin<Project> {
DocumentPluginGoals task = project.getTasks().create("documentPluginGoals", DocumentPluginGoals.class);
File pluginXml = new File(generatePluginDescriptorTask.getOutputs().getFiles().getSingleFile(), "plugin.xml");
task.setPluginXml(pluginXml);
task.setOutputDir(new File(project.getBuildDir(), "docs/generated/goals/"));
task.setOutputDir(new File(project.getBuildDir(), "generated/docs/maven-plugin-goals/"));
task.dependsOn(generatePluginDescriptorTask);
}

View File

@ -0,0 +1,80 @@
# === INCLUDE-CODE LOCATIONS ===
include-java=ROOT:example$java/org/springframework/boot/docs
include-kotlin= ROOT:example$kotlin/org/springframework/boot/docs
# === URLs ===
url-ant-docs=https://ant.apache.org/manual
url-buildpacks-docs=https://buildpacks.io/docs
url-dynatrace-docs=https://docs.dynatrace.com/docs
url-dynatrace-docs-shortlink={url-dynatrace-docs}/shortlink
url-github-raw=https://raw.githubusercontent.com/{github-repo}/{github-ref}
url-github-issues=https://github.com/{github-repo}/issues
url-github-wiki=https://github.com/{github-repo}/wiki
url-github=https://github.com/{github-repo}
url-graal-docs=https://www.graalvm.org/{version-graal}/reference-manual
url-graal-docs-native-image={url-graal-docs}/native-image
url-gradle-docs=https://docs.gradle.org/current/userguide
url-gradle-docs-application-plugin={url-gradle-docs}/application_plugin.html
url-gradle-docs-groovy-plugin={url-gradle-docs}/groovy_plugin.html
url-gradle-docs-java-plugin={url-gradle-docs}/java_plugin.html
url-gradle-docs-war-plugin={url-gradle-docs}/war_plugin.html
url-gradle-dsl=https://docs.gradle.org/current/dsl
url-gradle-javadoc=https://docs.gradle.org/current/javadoc
url-kotlin-docs-kotlin-plugin={url-kotlin-docs}/using-gradle.html
url-micrometer-docs-concepts={url-micrometer-docs}/concepts
url-micrometer-docs-implementations={url-micrometer-docs}/implementations
url-download-liberica-nik=https://bell-sw.com/pages/downloads/native-image-kit/#/nik-22-17
url-native-build-tools-docs=https://graalvm.github.io/native-build-tools/{version-native-build-tools}
url-native-build-tools-docs-gradle-plugin={url-native-build-tools-docs}/gradle-plugin.html
url-native-build-tools-docs-maven-plugin={url-native-build-tools-docs}/maven-plugin.html
url-paketo-docs=https://paketo.io/docs
url-paketo-docs-java-buildpack={url-paketo-docs}/buildpacks/language-family-buildpacks/java
url-spring-boot-for-apache-geode-docs=https://docs.spring.io/spring-boot-data-geode-build/2.0.x/reference/html5
url-spring-boot-for-apache-geode-site=https://github.com/spring-projects/spring-boot-data-geode
url-spring-data-cassandra-site=https://spring.io/projects/spring-data-cassandra
url-spring-data-commons-javadoc=https://docs.spring.io/spring-data/commons/docs/{version-spring-data-commons}/api
url-spring-data-couchbase-docs=https://docs.spring.io/spring-data/couchbase/reference/{version-spring-data-couchbase}
url-spring-data-couchbase-site=https://spring.io/projects/spring-data-couchbase
url-spring-data-elasticsearch-docs=https://docs.spring.io/spring-data/elasticsearch/reference/{version-spring-data-elasticsearch}
url-spring-data-elasticsearch-site=https://spring.io/projects/spring-data-elasticsearch
url-spring-data-envers-site=https://spring.io/projects/spring-data-envers
url-spring-data-gemfire-site=https://spring.io/projects/spring-data-gemfire
url-spring-data-geode-site=https://spring.io/projects/spring-data-geode
url-spring-data-jdbc-docs=https://docs.spring.io/spring-data/relational/reference/{version-spring-data-jdbc}
url-spring-data-jpa-javadoc=https://docs.spring.io/spring-data/jpa/docs/{version-spring-data-jpa}/api
url-spring-data-jpa-site=https://spring.io/projects/spring-jpa
url-spring-data-jpa-docs=https://docs.spring.io/spring-data/jpa/reference/{version-spring-data-jpa}
url-spring-data-ldap-site=https://spring.io/projects/spring-data-ldap
url-spring-data-mongodb-javadoc=https://docs.spring.io/spring-data/mongodb/docs/{version-spring-data-mongodb}/api
url-spring-data-mongodb-site=https://spring.io/projects/spring-data-mongodb
url-spring-data-mongodb-docs=https://docs.spring.io/spring-data/mongodb/reference/{version-spring-data-mongodb}
url-spring-data-neo4j-docs=https://docs.spring.io/spring-data/neo4j/reference/{version-spring-data-neo4j}
url-spring-data-neo4j-site=https://spring.io/projects/spring-data-neo4j
url-spring-data-r2dbc-javadoc=https://docs.spring.io/spring-data/r2dbc/docs/{version-spring-data-r2dbc}/api
url-spring-data-r2dbc-docs=https://docs.spring.io/spring-data/relational/reference/{version-spring-data-r2dbc}
url-spring-data-redis-site=https://spring.io/projects/spring-data-redis
url-spring-data-rest-javadoc=https://docs.spring.io/spring-data/rest/docs/{version-spring-data-rest}/api
url-spring-data-site=https://spring.io/projects/spring-data
# === API References ===
apiref-gradle-plugin-boot-build-image=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.html
apiref-gradle-plugin-boot-jar=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootJar.html
apiref-gradle-plugin-boot-run=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/run/BootRun.html
apiref-gradle-plugin-boot-war=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/bundling/BootWar.html
apiref-gradle-plugin-boot-build-info=xref:gradle-plugin:api/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.html
apiref-openjdk=https://docs.oracle.com/en/java/javase/17/docs/api
# === Code Links ===
code-spring-boot=https://github.com/{github-repo}/tree/{github-ref}
code-spring-boot-src={code-spring-boot}/spring-boot-project/spring-boot/src/main/java/org/springframework/boot
code-spring-boot-actuator-autoconfigure-src={code-spring-boot}/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure
code-spring-boot-actuator-src={code-spring-boot}/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate
code-spring-boot-autoconfigure-src={code-spring-boot}/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure
code-spring-boot-devtools-src={code-spring-boot}/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools
code-spring-boot-test-autoconfigure-src={code-spring-boot}/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure
code-spring-boot-latest=https://github.com/{github-repo}/tree/main

View File

@ -0,0 +1,27 @@
antora:
extensions:
site:
title: Spring Boot
content:
sources: []
asciidoc:
sourcemap: true
attributes:
chomp: all
hide-uri-scheme: '@'
page-pagination: ''
page-stackoverflow-url: https://stackoverflow.com/tags/spring-boot
tabs-sync-option: '@'
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
- '@springio/asciidoctor-extensions/configuration-properties-extension'
- '@springio/asciidoctor-extensions/section-ids-extension'
urls:
latest_version_segment: ''
runtime:
log:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.11/ui-bundle.zip

View File

@ -0,0 +1,171 @@
/*
* Copyright 2012-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.antora;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.LibraryVersion;
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
import org.springframework.boot.build.bom.Library.VersionAlignment;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AntoraAsciidocAttributes}.
*
* @author Phillip Webb
*/
class AntoraAsciidocAttributesTests {
@Test
void githubRefWhenReleasedVersionIsTag() {
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, null,
mockDependencyVersions(), null);
assertThat(attributes.get()).containsEntry("github-ref", "v1.2.3");
}
@Test
void githubRefWhenLatestSnapshotVersionIsMainBranch() {
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, null,
mockDependencyVersions(), null);
assertThat(attributes.get()).containsEntry("github-ref", "main");
}
@Test
void githubRefWhenOlderSnapshotVersionIsBranch() {
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", false, 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, 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, List.of(library),
mockDependencyVersions(), null);
assertThat(attributes.get()).containsEntry("version-spring-framework", "1.2.3");
}
@Test
void versionReferenceFromSpringDataDependencyVersion() {
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, null,
mockDependencyVersions(), null);
assertThat(attributes.get()).containsEntry("version-spring-data-mongodb", "1.2.3");
}
@Test
void versionNativeBuildTools() {
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, null,
mockDependencyVersions(), Map.of("nativeBuildToolsVersion", "3.4.5"));
assertThat(attributes.get()).containsEntry("version-native-build-tools", "3.4.5");
}
@Test
void urlArtifactReposiroryWhenRelease() {
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3", true, null,
mockDependencyVersions(), null);
assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.maven.apache.org/maven2");
}
@Test
void urlArtifactReposiroryWhenMilestone() {
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-M1", true, null,
mockDependencyVersions(), null);
assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.spring.io/milestone");
}
@Test
void urlArtifactReposiroryWhenSnapshot() {
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, null,
mockDependencyVersions(), null);
assertThat(attributes.get()).containsEntry("url-artifact-repository", "https://repo.spring.io/snapshot");
}
@Test
void urlLinksFromLibrary() {
Map<String, Function<LibraryVersion, String>> links = new LinkedHashMap<>();
links.put("site", (version) -> "https://example.com/site/" + version);
links.put("docs", (version) -> "https://example.com/docs/" + version);
Library library = mockLibrary(links);
AntoraAsciidocAttributes attributes = new AntoraAsciidocAttributes("1.2.3.1-SNAPSHOT", false, 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");
}
@Test
void linksFromProperties() {
Map<String, String> attributes = new AntoraAsciidocAttributes("1.2.3-SNAPSHOT", true, null,
mockDependencyVersions(), null)
.get();
assertThat(attributes).containsEntry("include-java", "ROOT:example$java/org/springframework/boot/docs");
assertThat(attributes).containsEntry("url-spring-data-cassandra-site",
"https://spring.io/projects/spring-data-cassandra");
List<String> keys = new ArrayList<>(attributes.keySet());
assertThat(keys.indexOf("include-java")).isLessThan(keys.indexOf("code-spring-boot-latest"));
}
private Library mockLibrary(Map<String, Function<LibraryVersion, String>> links) {
String name = "Spring Framework";
String calendarName = null;
LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3"));
List<Group> groups = Collections.emptyList();
List<ProhibitedVersion> prohibitedVersion = Collections.emptyList();
boolean considerSnapshots = false;
VersionAlignment versionAlignment = null;
String linkRootName = null;
Library library = new Library(name, calendarName, version, groups, prohibitedVersion, considerSnapshots,
versionAlignment, linkRootName, links);
return library;
}
private Map<String, String> mockDependencyVersions() {
Map<String, String> versions = new LinkedHashMap<>();
addMockSpringDataVersion(versions, "spring-data-commons");
addMockSpringDataVersion(versions, "spring-data-couchbase");
addMockSpringDataVersion(versions, "spring-data-elasticsearch");
addMockSpringDataVersion(versions, "spring-data-jdbc");
addMockSpringDataVersion(versions, "spring-data-jpa");
addMockSpringDataVersion(versions, "spring-data-mongodb");
addMockSpringDataVersion(versions, "spring-data-neo4j");
addMockSpringDataVersion(versions, "spring-data-r2dbc");
addMockSpringDataVersion(versions, "spring-data-rest-core");
return versions;
}
private void addMockSpringDataVersion(Map<String, String> versions, String artifactId) {
versions.put("org.springframework.data:" + artifactId, "1.2.3");
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2012-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.antora;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import org.gradle.api.Project;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.util.function.ThrowingConsumer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link GenerateAntoraPlaybook}.
*
* @author Phillip Webb
*/
class GenerateAntoraPlaybookTests {
@TempDir
File temp;
@Test
void writePlaybookGeneratesExpectedContent() throws Exception {
writePlaybookYml((task) -> {
task.getXrefStubs().addAll("appendix:.*", "api:.*", "reference:.*");
task.getAlwaysInclude().set(Map.of("name", "test", "classifier", "local-aggregate-content"));
});
Path actual = this.temp.toPath()
.resolve("rootproject/project/build/generated/docs/antora-playbook/antora-playbook.yml");
System.out.println(Files.readString(actual));
assertThat(actual).hasSameTextualContentAs(
Path.of("src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml"));
}
private void writePlaybookYml(ThrowingConsumer<GenerateAntoraPlaybook> customizer) throws Exception {
File rootProjectDir = new File(this.temp, "rootproject").getCanonicalFile();
rootProjectDir.mkdirs();
Project rootProject = ProjectBuilder.builder().withProjectDir(rootProjectDir).build();
File projectDir = new File(rootProjectDir, "project");
projectDir.mkdirs();
Project project = ProjectBuilder.builder().withProjectDir(projectDir).withParent(rootProject).build();
GenerateAntoraPlaybook task = project.getTasks().create("generateAntoraPlaybook", GenerateAntoraPlaybook.class);
customizer.accept(task);
task.writePlaybookYml();
}
}

View File

@ -153,7 +153,6 @@ class ArchitectureCheckTests {
Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name);
FileSystemUtils.copyRecursively(root.getFile(),
new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name));
}
private interface Callback<T> {

View File

@ -0,0 +1,50 @@
antora:
extensions:
- require: '@springio/antora-extensions/static-page-extension'
- require: '@springio/antora-xref-extension'
stub:
- appendix:.*
- api:.*
- reference:.*
- require: '@springio/antora-zip-contents-collector-extension'
always_include:
- classifier: local-aggregate-content
name: test
locations:
- project/build/generated/docs/antora-content/test-${version}-${name}-${classifier}.zip
- project/build/generated/docs/antora-dependencies-content/test-${version}-${name}-${classifier}.zip
version_file: gradle.properties
- require: '@springio/antora-extensions/root-component-extension'
root_component_name: spring-boot
site:
title: Spring Boot
content:
sources:
- url: ./../../../../..
branches: HEAD
version: unspecified
start_paths:
- project/src/docs/antora
asciidoc:
sourcemap: true
attributes:
chomp: all
hide-uri-scheme: '@'
page-pagination: ''
page-stackoverflow-url: https://stackoverflow.com/tags/spring-boot
tabs-sync-option: '@'
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
- '@springio/asciidoctor-extensions/configuration-properties-extension'
- '@springio/asciidoctor-extensions/section-ids-extension'
urls:
latest_version_segment: ''
runtime:
log:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.11/ui-bundle.zip
output:
dir: ./../../../site

View File

@ -1,4 +1,5 @@
version=3.3.0-SNAPSHOT
latestVersion=true
org.gradle.caching=true
org.gradle.parallel=true
@ -6,6 +7,7 @@ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
assertjVersion=3.25.3
commonsCodecVersion=1.16.1
graalVersion=22.3
hamcrestVersion=2.2
jacksonVersion=2.17.0
junitJupiterVersion=5.10.2

View File

@ -194,14 +194,6 @@ bom {
]
}
}
library("Spring Asciidoctor Extensions", "0.6.3") {
group("io.spring.asciidoctor") {
modules = [
"spring-asciidoctor-extensions-spring-boot",
"spring-asciidoctor-extensions-section-ids"
]
}
}
}
dependencies {