From 8d64e9971450bc54f4447930eec741bc2bd48e31 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 19 Mar 2024 22:29:48 -0700 Subject: [PATCH] Prepare buildSrc for migration to Antora Replace `AsciidoctorConventions` with `AntoraConventions` in preparation for the migration to Antora. See gh-33766 --- buildSrc/build.gradle | 9 +- .../boot/build/AntoraConventions.java | 155 +++++++++++++ .../boot/build/AsciidoctorConventions.java | 159 ------------- .../boot/build/ConventionsPlugin.java | 8 +- .../antora/AntoraAsciidocAttributes.java | 162 ++++++++++++++ .../boot/build/antora/Extensions.java | 197 +++++++++++++++++ .../build/antora/GenerateAntoraPlaybook.java | 209 ++++++++++++++++++ .../boot/build/artifacts/ArtifactRelease.java | 48 ++-- .../DocumentAutoConfigurationClasses.java | 6 +- .../ExtractVersionConstraints.java | 15 +- .../build/context/properties/Snippets.java | 8 +- .../DocumentDevtoolsPropertyDefaults.java | 4 +- .../mavenplugin/DocumentPluginGoals.java | 24 +- .../build/mavenplugin/MavenPluginPlugin.java | 4 +- .../antora-asciidoc-attributes.properties | 80 +++++++ .../build/antora/antora-playbook-template.yml | 27 +++ .../antora/AntoraAsciidocAttributesTests.java | 171 ++++++++++++++ .../antora/GenerateAntoraPlaybookTests.java | 68 ++++++ .../architecture/ArchitectureCheckTests.java | 1 - .../boot/build/antora/expected-playbook.yml | 50 +++++ gradle.properties | 2 + .../spring-boot-parent/build.gradle | 8 - 22 files changed, 1187 insertions(+), 228 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java delete mode 100644 buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.java create mode 100644 buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties create mode 100644 buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.java create mode 100644 buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index e0c51fe271f..a98fe030b4d 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -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}") diff --git a/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java new file mode 100644 index 00000000000..6bb73305f6d --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java @@ -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 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 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 defaultYml = new LinkedHashMap<>(); + defaultYml.put("title", "Spring Boot"); + if (navFile != null) { + defaultYml.put("nav", List.of(navFile)); + } + return defaultYml; + } + + private Map getAsciidocAttributes(Project project, + ExtractVersionConstraints dependencyVersionsTask) { + BomExtension bom = (BomExtension) project.project(DEPENDENCIES_PATH).getExtensions().getByName("bom"); + Map 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 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()); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java deleted file mode 100644 index 8fb4d4d9e4e..00000000000 --- a/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java +++ /dev/null @@ -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: - * - *
    - *
  • All warnings are made fatal. - *
  • The version of AsciidoctorJ is upgraded to 2.4.3. - *
  • An {@code asciidoctorExtensions} configuration is created. - *
  • For each {@link AsciidoctorTask} (HTML only): - *
      - *
    • A task is created to sync the documentation resources to its output directory. - *
    • {@code doctype} {@link AsciidoctorTask#options(Map) option} is configured. - *
    • The {@code backend} is configured. - *
    - *
  • For each {@link AbstractAsciidoctorTask} (HTML and PDF): - *
      - *
    • {@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. - *
    • {@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()} - * is enabled. - *
    • {@code asciidoctorExtensions} is added to the task's configurations. - *
    - *
- * - * @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 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; - } - -} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java index 03c5ab14819..8b4769be00e 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java @@ -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 { 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); 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 new file mode 100644 index 00000000000..5243e19a391 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java @@ -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 libraries; + + private final Map dependencyVersions; + + private final Map projectProperties; + + public AntoraAsciidocAttributes(Project project, BomExtension dependencyBom, + Map 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 libraries, + Map dependencyVersions, Map 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 get() { + Map attributes = new LinkedHashMap<>(); + addGitHubAttributes(attributes); + addVersionAttributes(attributes); + addUrlArtifactRepository(attributes); + addUrlLibraryLinkAttributes(attributes); + addPropertyAttributes(attributes); + return attributes; + } + + private void addGitHubAttributes(Map 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 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 attributes, String artifactId) { + addSpringDataDependencyVersion(attributes, artifactId, artifactId); + } + + private void addSpringDataDependencyVersion(Map 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 attributes) { + attributes.put("url-artifact-repository", this.artifactRelease.getDownloadRepo()); + } + + private void addUrlLibraryLinkAttributes(Map attributes) { + this.libraries.forEach((library) -> { + String prefix = "url-" + library.getLinkRootName() + "-"; + library.getLinks().forEach((name, link) -> attributes.put(prefix + name, link)); + }); + } + + private void addPropertyAttributes(Map 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); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java new file mode 100644 index 00000000000..89349b8e0e6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/Extensions.java @@ -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 antora; + static { + List 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 asciidoc; + static { + List 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 localOverrides = Collections.emptyMap(); + + private Extensions() { + } + + public static Map packages() { + Map 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> antora(Consumer extensions) { + AntoraExtensionsConfiguration result = new AntoraExtensionsConfiguration( + antora.stream().flatMap(Extension::names).sorted().toList()); + extensions.accept(result); + return result.config(); + } + + static List asciidoc() { + return asciidoc.stream().flatMap(Extension::names).sorted().toList(); + } + + private record Extension(String name, String version, String... includeNames) { + + Stream names() { + return (this.includeNames.length != 0) ? Arrays.stream(this.includeNames) : Stream.of(this.name); + } + + } + + static final class AntoraExtensionsConfiguration { + + private Map> extensions = new TreeMap<>(); + + private AntoraExtensionsConfiguration(List names) { + names.forEach((name) -> this.extensions.put(name, null)); + } + + void xref(Consumer xref) { + xref.accept(new Xref()); + } + + void zipContentsCollector(Consumer zipContentsCollector) { + zipContentsCollector.accept(new ZipContentsCollector()); + } + + void rootComponent(Consumer rootComponent) { + rootComponent.accept(new RootComponent()); + } + + List> config() { + List> config = new ArrayList<>(); + Map> orderedExtensions = new LinkedHashMap<>(this.extensions); + // The root component extension must be last + Map rootComponentConfig = orderedExtensions.remove(ROOT_COMPONENT_EXTENSION); + orderedExtensions.put(ROOT_COMPONENT_EXTENSION, rootComponentConfig); + orderedExtensions.forEach((name, customizations) -> { + Map 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 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 locations) { + customize("locations", locations); + } + + void alwaysInclude(Map 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); + } + + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.java new file mode 100644 index 00000000000..2d830250f9b --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/GenerateAntoraPlaybook.java @@ -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 getContentSourceConfiguration(); + + @Input + @Optional + public abstract ListProperty getXrefStubs(); + + @Input + @Optional + public abstract MapProperty 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 getData() throws IOException { + Map data = loadPlaybookTemplate(); + addExtensions(data); + addSources(data); + addDir(data); + return data; + } + + @SuppressWarnings("unchecked") + private Map loadPlaybookTemplate() throws IOException { + try (InputStream resource = getClass().getResourceAsStream("antora-playbook-template.yml")) { + return createYaml().loadAs(resource, LinkedHashMap.class); + } + } + + @SuppressWarnings("unchecked") + private void addExtensions(Map data) { + Map antora = (Map) 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 asciidoc = (Map) data.get("asciidoc"); + asciidoc.put("extensions", Extensions.asciidoc()); + } + + private void addSources(Map data) { + List> contentSources = getList(data, "content.sources"); + contentSources.add(createContentSource()); + } + + private Map createContentSource() { + Map 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 startPaths = new LinkedHashSet<>(); + addAntoraContentStartPaths(startPaths); + startPaths.add(relativizeFromRootProject(antoraSrc).toString()); + source.put("start_paths", startPaths.stream().toList()); + return source; + } + + private void addAntoraContentStartPaths(Set 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 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 List getList(Map data, String location) { + return (List) get(data, location); + } + + @SuppressWarnings("unchecked") + private Object get(Map data, String location) { + Object result = data; + String[] keys = location.split("\\."); + for (String key : keys) { + result = ((Map) 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); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java b/buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java index 3cf861543b0..a0def21d15e 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/artifacts/ArtifactRelease.java @@ -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; + } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java index ce8fbc41461..7e771d0283e 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java @@ -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("|==="); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java b/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java index 4d14b2977d4..5114a33b74c 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java @@ -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)); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java index 3e7d7ebf199..a86560c2d6a 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java @@ -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)) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java b/buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java index 515228ce70a..bbb622b228a 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/devtools/DocumentDevtoolsPropertyDefaults.java @@ -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 dependency = new HashMap<>(); dependency.put("path", ":spring-boot-project:spring-boot-devtools"); dependency.put("configuration", "propertyDefaults"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java index f5256fc17c8..11c75f12aad 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java @@ -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 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 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 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; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java index 6301deb2b4b..8bb4b7507b6 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java @@ -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 { 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); } diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties new file mode 100644 index 00000000000..6211277691b --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties @@ -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 + diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml new file mode 100644 index 00000000000..dba5023b04c --- /dev/null +++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-playbook-template.yml @@ -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 \ No newline at end of file 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 new file mode 100644 index 00000000000..0f3ba0baccf --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java @@ -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> 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 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 keys = new ArrayList<>(attributes.keySet()); + assertThat(keys.indexOf("include-java")).isLessThan(keys.indexOf("code-spring-boot-latest")); + } + + private Library mockLibrary(Map> links) { + String name = "Spring Framework"; + String calendarName = null; + LibraryVersion version = new LibraryVersion(DependencyVersion.parse("1.2.3")); + List groups = Collections.emptyList(); + List 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 mockDependencyVersions() { + Map 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 versions, String artifactId) { + versions.put("org.springframework.data:" + artifactId, "1.2.3"); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.java b/buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.java new file mode 100644 index 00000000000..5c0ad719827 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/GenerateAntoraPlaybookTests.java @@ -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 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(); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java index 085db6c66e1..11f15a8ade9 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java @@ -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 { diff --git a/buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml b/buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml new file mode 100644 index 00000000000..a5a42c8a262 --- /dev/null +++ b/buildSrc/src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml @@ -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 diff --git a/gradle.properties b/gradle.properties index 0f7c4b7053b..b74d783c90e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/spring-boot-project/spring-boot-parent/build.gradle b/spring-boot-project/spring-boot-parent/build.gradle index ad07a0d65c9..562e2f57e28 100644 --- a/spring-boot-project/spring-boot-parent/build.gradle +++ b/spring-boot-project/spring-boot-parent/build.gradle @@ -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 {