Rework Antora Gradle Infrastructure

Closes gh-40572

Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
This commit is contained in:
Andy Wilkinson 2024-10-31 17:36:51 +00:00
parent 6470748d6d
commit f9281a61ff
21 changed files with 1135 additions and 290 deletions

View File

@ -70,6 +70,14 @@ gradlePlugin {
id = "org.springframework.boot.annotation-processor"
implementationClass = "org.springframework.boot.build.processors.AnnotationProcessorPlugin"
}
antoraAggregatedPlugin {
id = "org.springframework.boot.antora-contributor"
implementationClass = "org.springframework.boot.build.antora.AntoraContributorPlugin"
}
antoraAggregatorPlugin {
id = "org.springframework.boot.antora-dependencies"
implementationClass = "org.springframework.boot.build.antora.AntoraDependenciesPlugin"
}
architecturePlugin {
id = "org.springframework.boot.architecture"
implementationClass = "org.springframework.boot.build.architecture.ArchitecturePlugin"

View File

@ -58,10 +58,18 @@ public class AntoraConventions {
private static final String DEPENDENCIES_PATH = ":spring-boot-project:spring-boot-dependencies";
private static final String ANTORA_SOURCE_DIR = "src/docs/antora";
private static final List<String> NAV_FILES = List.of("nav.adoc", "local-nav.adoc");
/**
* Default Antora source directory.
*/
public static final String ANTORA_SOURCE_DIR = "src/docs/antora";
/**
* Name of the {@link GenerateAntoraPlaybook} task.
*/
public static final String GENERATE_ANTORA_PLAYBOOK_TASK_NAME = "generateAntoraPlaybook";
void apply(Project project) {
project.getPlugins().withType(AntoraPlugin.class, (antoraPlugin) -> apply(project, antoraPlugin));
}
@ -70,7 +78,7 @@ public class AntoraConventions {
ExtractVersionConstraints dependencyVersionsTask = addDependencyVersionsTask(project);
project.getPlugins().apply(GenerateAntoraYmlPlugin.class);
TaskContainer tasks = project.getTasks();
GenerateAntoraPlaybook generateAntoraPlaybookTask = tasks.create("generateAntoraPlaybook",
GenerateAntoraPlaybook generateAntoraPlaybookTask = tasks.create(GENERATE_ANTORA_PLAYBOOK_TASK_NAME,
GenerateAntoraPlaybook.class);
configureGenerateAntoraPlaybookTask(project, generateAntoraPlaybookTask);
Copy copyAntoraPackageJsonTask = tasks.create("copyAntoraPackageJson", Copy.class);

View File

@ -0,0 +1,32 @@
/*
* 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 org.gradle.api.Project;
/**
* A contribution of aggregate content.
*
* @author Andy Wilkinson
*/
class AggregateContentContribution extends ConsumableContentContribution {
protected AggregateContentContribution(Project project, String name) {
super(project, "aggregate", name);
}
}

View File

@ -0,0 +1,85 @@
/*
* 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 javax.inject.Inject;
import org.antora.gradle.AntoraPlugin;
import org.gradle.api.Action;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.CopySpec;
/**
* {@link Plugin} for a project that contributes to Antora-based documentation that is
* {@link AntoraDependenciesPlugin depended upon} by another project.
*
* @author Andy Wilkinson
*/
public class AntoraContributorPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(AntoraPlugin.class);
NamedDomainObjectContainer<Contribution> antoraContributions = project.getObjects()
.domainObjectContainer(Contribution.class,
(name) -> project.getObjects().newInstance(Contribution.class, name, project));
project.getExtensions().add("antoraContributions", antoraContributions);
}
public static class Contribution {
private final String name;
private final Project project;
@Inject
public Contribution(String name, Project project) {
this.name = name;
this.project = project;
}
public String getName() {
return this.name;
}
public void source() {
new SourceContribution(this.project, this.name).produce();
}
public void catalogContent(Action<CopySpec> action) {
CopySpec copySpec = this.project.copySpec();
action.execute(copySpec);
new CatalogContentContribution(this.project, this.name).produceFrom(copySpec);
}
public void aggregateContent(Action<CopySpec> action) {
CopySpec copySpec = this.project.copySpec();
action.execute(copySpec);
new AggregateContentContribution(this.project, this.name).produceFrom(copySpec);
}
public void localAggregateContent(Action<CopySpec> action) {
CopySpec copySpec = this.project.copySpec();
action.execute(copySpec);
new LocalAggregateContentContribution(this.project, this.name).produceFrom(copySpec);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* 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 javax.inject.Inject;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* {@link Plugin} for a project that depends on {@link AntoraContributorPlugin
* contributed} Antora-based documentation.
*
* @author Andy Wilkinson
*/
public class AntoraDependenciesPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
NamedDomainObjectContainer<AntoraDependency> antoraDependencies = project.getObjects()
.domainObjectContainer(AntoraDependency.class);
project.getExtensions().add("antoraDependencies", antoraDependencies);
}
public static class AntoraDependency {
private final String name;
private final Project project;
private String path;
@Inject
public AntoraDependency(String name, Project project) {
this.name = name;
this.project = project;
}
public String getName() {
return this.name;
}
public String getPath() {
return this.path;
}
public void setPath(String path) {
this.path = path;
}
public void catalogContent() {
new CatalogContentContribution(this.project, this.name).consumeFrom(this.path);
}
public void aggregateContent() {
new AggregateContentContribution(this.project, this.name).consumeFrom(this.path);
}
public void source() {
new SourceContribution(this.project, this.name).consumeFrom(this.path);
}
}
}

View File

@ -0,0 +1,32 @@
/*
* 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 org.gradle.api.Project;
/**
* A contribution of catalog content.
*
* @author Andy Wilkinson
*/
class CatalogContentContribution extends ConsumableContentContribution {
CatalogContentContribution(Project project, String name) {
super(project, "catalog", name);
}
}

View File

@ -0,0 +1,107 @@
/*
* 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 org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.file.CopySpec;
import org.gradle.api.file.Directory;
import org.gradle.api.file.RegularFile;
import org.gradle.api.provider.Provider;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
/**
* A contribution of content to Antora that can be consumed by other projects.
*
* @author Andy Wilkinson
*/
class ConsumableContentContribution extends ContentContribution {
protected ConsumableContentContribution(Project project, String type, String name) {
super(project, name, type);
}
@Override
void produceFrom(CopySpec copySpec) {
TaskProvider<? extends Task> producer = super.configureProduction(copySpec);
Configuration configuration = createConfiguration(getName(),
"Configuration for %s Antora %s content artifacts.");
configuration.setCanBeConsumed(true);
configuration.setCanBeResolved(false);
getProject().getArtifacts().add(configuration.getName(), producer);
}
void consumeFrom(String path) {
Configuration configuration = createConfiguration(getName(), "Configuration for %s Antora %s content.");
configuration.setCanBeConsumed(false);
configuration.setCanBeResolved(true);
DependencyHandler dependencies = getProject().getDependencies();
dependencies.add(configuration.getName(),
getProject().provider(() -> projectDependency(path, configuration.getName())));
Provider<Directory> outputDirectory = outputDirectory("content", getName());
TaskContainer tasks = getProject().getTasks();
TaskProvider<?> copyAntoraContent = tasks.register(taskName("copy", "%s", configuration.getName()),
CopyAntoraContent.class, (task) -> configureCopyContent(task, path, configuration, outputDirectory));
configureAntora(addInputFrom(copyAntoraContent, configuration.getName()));
configurePlaybookGeneration(this::addToZipContentsCollectorDependencies);
getProject().getExtensions()
.getByType(PublishingExtension.class)
.getPublications()
.withType(MavenPublication.class)
.configureEach((mavenPublication) -> addPublishedMavenArtifact(mavenPublication, copyAntoraContent));
}
private void configureCopyContent(CopyAntoraContent task, String path, Configuration configuration,
Provider<Directory> outputDirectory) {
task.setDescription(
"Syncs the %s Antora %s content from %s.".formatted(getName(), toDescription(getType()), path));
task.setSource(configuration);
task.getOutputFile().set(outputDirectory.map(this::getContentZipFile));
}
private void addToZipContentsCollectorDependencies(GenerateAntoraPlaybook task) {
task.getAntoraExtensions().getZipContentsCollector().getDependencies().add(getName());
}
private void addPublishedMavenArtifact(MavenPublication mavenPublication, TaskProvider<?> copyAntoraContent) {
if ("maven".equals(mavenPublication.getName())) {
String classifier = "%s-%s-content".formatted(getName(), getType());
mavenPublication.artifact(copyAntoraContent, (mavenArtifact) -> mavenArtifact.setClassifier(classifier));
}
}
private RegularFile getContentZipFile(Directory dir) {
Object version = getProject().getVersion();
return dir.file("spring-boot-docs-%s-%s-%s-content.zip".formatted(version, getName(), getType()));
}
private static String toDescription(String input) {
return input.replace("-", " ");
}
private Configuration createConfiguration(String name, String description) {
return getProject().getConfigurations()
.create(configurationName(name, "Antora%sContent", getType()),
(configuration) -> configuration.setDescription(description.formatted(getName(), getType())));
}
}

View File

@ -0,0 +1,65 @@
/*
* 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 org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.CopySpec;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.Zip;
/**
* A contribution of content to Antora.
*
* @author Andy Wilkinson
*/
abstract class ContentContribution extends Contribution {
private final String type;
protected ContentContribution(Project project, String name, String type) {
super(project, name);
this.type = type;
}
protected String getType() {
return this.type;
}
abstract void produceFrom(CopySpec copySpec);
protected TaskProvider<? extends Task> configureProduction(CopySpec copySpec) {
TaskContainer tasks = getProject().getTasks();
TaskProvider<Zip> zipContent = tasks.register(taskName("zip", "%sAntora%sContent", getName(), this.type),
Zip.class, (zip) -> {
zip.getDestinationDirectory()
.set(getProject().getLayout().getBuildDirectory().dir("generated/docs/antora-content"));
zip.getArchiveClassifier().set("%s-%s-content".formatted(getName(), this.type));
zip.with(copySpec);
zip.setDescription("Creates a zip archive of the %s Antora %s content.".formatted(getName(),
toDescription(this.type)));
});
configureAntora(addInputFrom(zipContent, zipContent.getName()));
return zipContent;
}
private static String toDescription(String input) {
return input.replace("-", " ");
}
}

View File

@ -0,0 +1,115 @@
/*
* 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.Arrays;
import java.util.Map;
import org.antora.gradle.AntoraTask;
import org.apache.commons.lang3.StringUtils;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.file.Directory;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskProvider;
import org.springframework.boot.build.AntoraConventions;
/**
* A contribution to Antora.
*
* @author Andy Wilkinson
*/
abstract class Contribution {
private final Project project;
private final String name;
protected Contribution(Project project, String name) {
this.project = project;
this.name = name;
}
protected Project getProject() {
return this.project;
}
protected String getName() {
return this.name;
}
protected Dependency projectDependency(String path, String configurationName) {
return getProject().getDependencies().project(Map.of("path", path, "configuration", configurationName));
}
protected Provider<Directory> outputDirectory(String dependencyType, String theName) {
return getProject().getLayout()
.getBuildDirectory()
.dir("generated/docs/antora-dependencies-" + dependencyType + "/" + theName);
}
protected String taskName(String verb, String object, String... args) {
return name(verb, object, args);
}
protected String configurationName(String name, String type, String... args) {
return name(toCamelCase(name), type, args);
}
protected void configurePlaybookGeneration(Action<GenerateAntoraPlaybook> action) {
this.project.getTasks()
.named(AntoraConventions.GENERATE_ANTORA_PLAYBOOK_TASK_NAME, GenerateAntoraPlaybook.class, action);
}
protected void configureAntora(Action<AntoraTask> action) {
this.project.getTasks().named("antora", AntoraTask.class, action);
}
protected Action<AntoraTask> addInputFrom(TaskProvider<?> task, String propertyName) {
return (antora) -> antora.getInputs()
.files(task)
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName(propertyName);
}
private String name(String prefix, String format, String... args) {
return prefix + format.formatted(Arrays.stream(args).map(this::toPascalCase).toArray());
}
private String toPascalCase(String input) {
return StringUtils.capitalize(toCamelCase(input));
}
private String toCamelCase(String input) {
StringBuilder output = new StringBuilder(input.length());
boolean capitalize = false;
for (char c : input.toCharArray()) {
if (c == '-') {
capitalize = true;
}
else {
output.append(capitalize ? Character.toUpperCase(c) : c);
capitalize = false;
}
}
return output.toString();
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
/**
* Tasks to copy Antora content.
*
* @author Andy Wilkinson
*/
public abstract class CopyAntoraContent extends DefaultTask {
private FileCollection source;
@Inject
public CopyAntoraContent() {
}
@InputFiles
public FileCollection getSource() {
return this.source;
}
public void setSource(FileCollection source) {
this.source = source;
}
@OutputFile
public abstract RegularFileProperty getOutputFile();
@TaskAction
void copyAntoraContent() throws IllegalStateException, IOException {
Path source = this.source.getSingleFile().toPath();
Path target = getOutputFile().getAsFile().get().toPath();
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}

View File

@ -16,7 +16,7 @@
package org.springframework.boot.build.antora;
import java.nio.file.Path;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -158,20 +158,24 @@ public final class Extensions {
customize("version_file", versionFile);
}
void locations(Path... locations) {
locations(Arrays.stream(locations).map(Path::toString).toList());
}
private void locations(List<String> locations) {
void locations(List<String> locations) {
customize("locations", locations);
}
void alwaysInclude(Map<String, String> alwaysInclude) {
void alwaysInclude(List<AlwaysInclude> alwaysInclude) {
if (alwaysInclude != null && !alwaysInclude.isEmpty()) {
customize("always_include", List.of(new TreeMap<>(alwaysInclude)));
customize("always_include", alwaysInclude.stream().map(AlwaysInclude::asMap).toList());
}
}
record AlwaysInclude(String name, String classifier) implements Serializable {
private Map<String, String> asMap() {
return new TreeMap<>(Map.of("name", name(), "classifier", classifier()));
}
}
}
class RootComponent extends Customizer {

View File

@ -26,26 +26,31 @@ import java.nio.file.Path;
import java.util.ArrayList;
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 javax.inject.Inject;
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.Directory;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Nested;
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;
import org.springframework.boot.build.AntoraConventions;
import org.springframework.boot.build.antora.Extensions.AntoraExtensionsConfiguration.ZipContentsCollector.AlwaysInclude;
/**
* Task to generate a local Antora playbook.
*
@ -53,35 +58,61 @@ import org.yaml.snakeyaml.Yaml;
*/
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/";
private final Path root;
private final Provider<String> playbookOutputDir;
private final String version;
private final AntoraExtensions antoraExtensions;
private final AsciidocExtensions asciidocExtensions;
private final ContentSource contentSource;
@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();
@Input
@Optional
public abstract Property<Boolean> getExcludeJavadocExtension();
public GenerateAntoraPlaybook() {
this.root = toRealPath(getProject().getRootDir().toPath());
this.antoraExtensions = getProject().getObjects().newInstance(AntoraExtensions.class, this.root);
this.asciidocExtensions = getProject().getObjects().newInstance(AsciidocExtensions.class);
this.version = getProject().getVersion().toString();
this.playbookOutputDir = configurePlaybookOutputDir(getProject());
this.contentSource = getProject().getObjects().newInstance(ContentSource.class, this.root);
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");
this.contentSource.addStartPath(getProject()
.provider(() -> getProject().getLayout().getProjectDirectory().dir(AntoraConventions.ANTORA_SOURCE_DIR)));
}
@Nested
public AntoraExtensions getAntoraExtensions() {
return this.antoraExtensions;
}
@Nested
public AsciidocExtensions getAsciidocExtensions() {
return this.asciidocExtensions;
}
@Nested
public ContentSource getContentSource() {
return this.contentSource;
}
private Provider<String> configurePlaybookOutputDir(Project project) {
Path siteDirectory = getProject().getLayout().getBuildDirectory().dir("site").get().getAsFile().toPath();
return project.provider(() -> {
Path playbookDir = toRealPath(getOutputFile().get().getAsFile().toPath()).getParent();
Path outputDir = toRealPath(siteDirectory);
return "." + File.separator + playbookDir.relativize(outputDir).toString();
});
}
@TaskAction
@ -93,26 +124,14 @@ public abstract class GenerateAntoraPlaybook extends DefaultTask {
}
}
@Input
final Map<String, Object> getData() throws IOException {
private Map<String, Object> getData() throws IOException {
Map<String, Object> data = loadPlaybookTemplate();
addExtensions(data);
addSources(data);
addDir(data);
filterJavadocExtension(data);
return data;
}
@SuppressWarnings("unchecked")
private void filterJavadocExtension(Map<String, Object> data) {
if (getExcludeJavadocExtension().getOrElse(Boolean.FALSE)) {
Map<String, Object> asciidoc = (Map<String, Object>) data.get("asciidoc");
List<String> extensions = new ArrayList<>((List<String>) asciidoc.get("extensions"));
extensions.remove("@springio/asciidoctor-extensions/javadoc-extension");
asciidoc.put("extensions", extensions);
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> loadPlaybookTemplate() throws IOException {
try (InputStream resource = getClass().getResourceAsStream("antora-playbook-template.yml")) {
@ -124,21 +143,25 @@ public abstract class GenerateAntoraPlaybook extends DefaultTask {
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.xref(
(xref) -> xref.stub(this.antoraExtensions.getXref().getStubs().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 antoraDependencies = getRelativeProjectPath()
.resolve(GENERATED_DOCS + "antora-dependencies-content/" + locationName);
zipContentsCollector.locations(antoraContent, antoraDependencies);
zipContentsCollector.alwaysInclude(getAlwaysInclude().getOrNull());
zipContentsCollector.locations(this.antoraExtensions.getZipContentsCollector()
.getLocations()
.getOrElse(Collections.emptyList()));
zipContentsCollector
.alwaysInclude(this.antoraExtensions.getZipContentsCollector().getAlwaysInclude().getOrNull());
});
extensions.rootComponent((rootComponent) -> rootComponent.name("boot"));
}));
Map<String, Object> asciidoc = (Map<String, Object>) data.get("asciidoc");
asciidoc.put("extensions", Extensions.asciidoc());
List<String> asciidocExtensions = Extensions.asciidoc();
if (this.asciidocExtensions.getExcludeJavadocExtension().getOrElse(Boolean.FALSE)) {
asciidocExtensions = new ArrayList<>(asciidocExtensions);
asciidocExtensions.remove("@springio/asciidoctor-extensions/javadoc-extension");
}
asciidoc.put("extensions", asciidocExtensions);
}
private void addSources(Map<String, Object> data) {
@ -149,34 +172,17 @@ public abstract class GenerateAntoraPlaybook extends DefaultTask {
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(File.separator).append(".."));
this.root.relativize(playbookPath).normalize().forEach((path) -> url.append(File.separator).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());
source.put("version", this.version);
source.put("start_paths", this.contentSource.getStartPaths().get());
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().getLayout().getBuildDirectory().dir("site").get().getAsFile().toPath());
data.put("output", Map.of("dir", "." + File.separator + playbookDir.relativize(outputDir).toString()));
data.put("output", Map.of("dir", this.playbookOutputDir.get()));
}
@SuppressWarnings("unchecked")
@ -201,20 +207,7 @@ public abstract class GenerateAntoraPlaybook extends DefaultTask {
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) {
private static Path toRealPath(Path path) {
try {
return Files.exists(path) ? path.toRealPath() : path;
}
@ -223,4 +216,112 @@ public abstract class GenerateAntoraPlaybook extends DefaultTask {
}
}
public abstract static class AntoraExtensions {
private final Xref xref;
private final ZipContentsCollector zipContentsCollector;
@Inject
public AntoraExtensions(ObjectFactory objects, Path root) {
this.xref = objects.newInstance(Xref.class);
this.zipContentsCollector = objects.newInstance(ZipContentsCollector.class, root);
}
@Nested
public Xref getXref() {
return this.xref;
}
@Nested
public ZipContentsCollector getZipContentsCollector() {
return this.zipContentsCollector;
}
public abstract static class Xref {
@Input
@Optional
public abstract ListProperty<String> getStubs();
}
public abstract static class ZipContentsCollector {
private final Provider<List<String>> locations;
@Inject
public ZipContentsCollector(Project project, Path root) {
this.locations = configureZipContentCollectorLocations(project, root);
}
private Provider<List<String>> configureZipContentCollectorLocations(Project project, Path root) {
ListProperty<String> locations = project.getObjects().listProperty(String.class);
Path relativeProjectPath = relativize(root, project.getProjectDir().toPath());
String locationName = project.getName() + "-${version}-${name}-${classifier}.zip";
locations.add(project
.provider(() -> relativeProjectPath.resolve(GENERATED_DOCS + "antora-content/" + locationName)
.toString()));
locations.addAll(getDependencies().map((dependencies) -> dependencies.stream()
.map((dependency) -> relativeProjectPath
.resolve(GENERATED_DOCS + "antora-dependencies-content/" + dependency + "/" + locationName))
.map(Path::toString)
.toList()));
return locations;
}
private static Path relativize(Path root, Path subPath) {
return toRealPath(root).relativize(toRealPath(subPath)).normalize();
}
@Input
@Optional
public abstract ListProperty<AlwaysInclude> getAlwaysInclude();
@Input
@Optional
public Provider<List<String>> getLocations() {
return this.locations;
}
@Input
@Optional
public abstract SetProperty<String> getDependencies();
}
}
public abstract static class AsciidocExtensions {
@Inject
public AsciidocExtensions() {
}
@Input
@Optional
public abstract Property<Boolean> getExcludeJavadocExtension();
}
public abstract static class ContentSource {
private final Path root;
@Inject
public ContentSource(Path root) {
this.root = root;
}
@Input
public abstract ListProperty<String> getStartPaths();
void addStartPath(Provider<Directory> startPath) {
getStartPaths()
.add(startPath.map((dir) -> this.root.relativize(toRealPath(dir.getAsFile().toPath())).toString()));
}
}
}

View File

@ -0,0 +1,48 @@
/*
* 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 org.gradle.api.Project;
import org.gradle.api.file.CopySpec;
import org.springframework.boot.build.antora.Extensions.AntoraExtensionsConfiguration.ZipContentsCollector.AlwaysInclude;
/**
* A contribution of aggregate content that cannot be consumed by other projects.
*
* @author Andy Wilkinson
*/
class LocalAggregateContentContribution extends ContentContribution {
protected LocalAggregateContentContribution(Project project, String name) {
super(project, name, "local-aggregate");
}
@Override
void produceFrom(CopySpec copySpec) {
super.configureProduction(copySpec);
configurePlaybookGeneration(this::addToAlwaysInclude);
}
private void addToAlwaysInclude(GenerateAntoraPlaybook task) {
task.getAntoraExtensions()
.getZipContentsCollector()
.getAlwaysInclude()
.add(new AlwaysInclude(getName(), "local-aggregate-content"));
}
}

View File

@ -0,0 +1,79 @@
/*
* 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 org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.file.Directory;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.Zip;
import org.springframework.boot.build.AntoraConventions;
/**
* A contribution of source to Antora.
*
* @author Andy Wilkinson
*/
class SourceContribution extends Contribution {
private static final String CONFIGURATION_NAME = "antoraSource";
SourceContribution(Project project, String name) {
super(project, name);
}
void produce() {
Configuration antoraSource = getProject().getConfigurations().create(CONFIGURATION_NAME);
TaskProvider<Zip> antoraSourceZip = getProject().getTasks().register("antoraSourceZip", Zip.class, (zip) -> {
zip.getDestinationDirectory().set(getProject().getLayout().getBuildDirectory().dir("antora-source"));
zip.from(AntoraConventions.ANTORA_SOURCE_DIR);
zip.setDescription(
"Creates a zip archive of the Antora source in %s.".formatted(AntoraConventions.ANTORA_SOURCE_DIR));
});
getProject().getArtifacts().add(antoraSource.getName(), antoraSourceZip);
}
void consumeFrom(String path) {
Configuration configuration = createConfiguration(getName());
DependencyHandler dependencies = getProject().getDependencies();
dependencies.add(configuration.getName(),
getProject().provider(() -> projectDependency(path, CONFIGURATION_NAME)));
Provider<Directory> outputDirectory = outputDirectory("source", getName());
TaskContainer tasks = getProject().getTasks();
TaskProvider<SyncAntoraSource> syncSource = tasks.register(taskName("sync", "%s", configuration.getName()),
SyncAntoraSource.class, (task) -> configureSyncSource(task, path, configuration, outputDirectory));
configureAntora(addInputFrom(syncSource, configuration.getName()));
configurePlaybookGeneration(
(generatePlaybook) -> generatePlaybook.getContentSource().addStartPath(outputDirectory));
}
private void configureSyncSource(SyncAntoraSource task, String path, Configuration configuration,
Provider<Directory> outputDirectory) {
task.setDescription("Syncs the %s Antora source from %s.".formatted(getName(), path));
task.setSource(configuration);
task.getOutputDirectory().set(outputDirectory);
}
private Configuration createConfiguration(String name) {
return getProject().getConfigurations().create(configurationName(name, "AntoraSource"));
}
}

View File

@ -0,0 +1,72 @@
/*
* 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 javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ArchiveOperations;
import org.gradle.api.file.CopySpec;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileSystemOperations;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
/**
* Task sync Antora source.
*
* @author Andy Wilkinson
*/
public abstract class SyncAntoraSource extends DefaultTask {
private final FileSystemOperations fileSystemOperations;
private final ArchiveOperations archiveOperations;
private FileCollection source;
@Inject
public SyncAntoraSource(FileSystemOperations fileSystemOperations, ArchiveOperations archiveOperations) {
this.fileSystemOperations = fileSystemOperations;
this.archiveOperations = archiveOperations;
}
@OutputDirectory
public abstract DirectoryProperty getOutputDirectory();
@InputFiles
public FileCollection getSource() {
return this.source;
}
public void setSource(FileCollection source) {
this.source = source;
}
@TaskAction
void syncAntoraSource() {
this.fileSystemOperations.sync(this::syncAntoraSource);
}
private void syncAntoraSource(CopySpec sync) {
sync.into(getOutputDirectory());
this.source.getFiles().forEach((file) -> sync.from(this.archiveOperations.zipTree(file)));
}
}

View File

@ -19,13 +19,15 @@ 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 java.util.List;
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.boot.build.antora.Extensions.AntoraExtensionsConfiguration.ZipContentsCollector.AlwaysInclude;
import org.springframework.boot.build.antora.GenerateAntoraPlaybook.AntoraExtensions.ZipContentsCollector;
import org.springframework.util.function.ThrowingConsumer;
import static org.assertj.core.api.Assertions.assertThat;
@ -43,23 +45,26 @@ class GenerateAntoraPlaybookTests {
@Test
void writePlaybookGeneratesExpectedContent() throws Exception {
writePlaybookYml((task) -> {
task.getXrefStubs().addAll("appendix:.*", "api:.*", "reference:.*");
task.getAlwaysInclude().set(Map.of("name", "test", "classifier", "local-aggregate-content"));
task.getAntoraExtensions().getXref().getStubs().addAll("appendix:.*", "api:.*", "reference:.*");
ZipContentsCollector zipContentsCollector = task.getAntoraExtensions().getZipContentsCollector();
zipContentsCollector.getAlwaysInclude().set(List.of(new AlwaysInclude("test", "local-aggregate-content")));
zipContentsCollector.getDependencies().add("test-dependency");
});
String actual = Files.readString(this.temp.toPath()
.resolve("rootproject/project/build/generated/docs/antora-playbook/antora-playbook.yml"));
String expected = Files
.readString(Path.of("src/test/resources/org/springframework/boot/build/antora/expected-playbook.yml"));
System.out.println(actual);
assertThat(actual.replace('\\', '/')).isEqualToNormalizingNewlines(expected.replace('\\', '/'));
}
@Test
void writePlaybookWhenHasJavadocExcludeGeneratesExpectedContent() throws Exception {
writePlaybookYml((task) -> {
task.getXrefStubs().addAll("appendix:.*", "api:.*", "reference:.*");
task.getAlwaysInclude().set(Map.of("name", "test", "classifier", "local-aggregate-content"));
task.getExcludeJavadocExtension().set(true);
task.getAntoraExtensions().getXref().getStubs().addAll("appendix:.*", "api:.*", "reference:.*");
ZipContentsCollector zipContentsCollector = task.getAntoraExtensions().getZipContentsCollector();
zipContentsCollector.getAlwaysInclude().set(List.of(new AlwaysInclude("test", "local-aggregate-content")));
zipContentsCollector.getDependencies().add("test-dependency");
task.getAsciidocExtensions().getExcludeJavadocExtension().set(true);
});
String actual = Files.readString(this.temp.toPath()
.resolve("rootproject/project/build/generated/docs/antora-playbook/antora-playbook.yml"));

View File

@ -13,7 +13,7 @@ antora:
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
- project/build/generated/docs/antora-dependencies-content/test-dependency/test-${version}-${name}-${classifier}.zip
version_file: gradle.properties
- require: '@springio/antora-extensions/root-component-extension'
root_component_name: boot

View File

@ -1,6 +1,6 @@
plugins {
id "java-library"
id "org.antora"
id "org.springframework.boot.antora-contributor"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
@ -9,10 +9,6 @@ plugins {
description = "Spring Boot Actuator AutoConfigure"
configurations {
antoraContent
}
dependencies {
api(project(":spring-boot-project:spring-boot-actuator"))
api(project(":spring-boot-project:spring-boot"))
@ -215,36 +211,18 @@ def documentationTest = tasks.register("documentationTest", Test) {
}
}
def antoraActuatorRestApiLocalAggregateContent = tasks.register("antoraActuatorRestApiLocalAggregateContent", Zip) {
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "actuator-rest-api-local-aggregate-content"
from(tasks.getByName("generateAntoraYml")) {
into "modules"
antoraContributions {
'actuator-rest-api' {
aggregateContent {
from(documentationTest.map { layout.buildDirectory.dir("generated-snippets") }) {
into "modules/api/partials/rest/actuator"
}
}
localAggregateContent {
from(tasks.named("generateAntoraYml")) {
into "modules"
}
}
source()
}
}
def antoraActuatorRestApiAggregateContent = tasks.register("antoraActuatorRestApiAggregateContent", Zip) {
dependsOn documentationTest
inputs.dir(layout.buildDirectory.dir("generated-snippets"))
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("generatedSnippets")
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "actuator-rest-api-aggregate-content"
from(layout.buildDirectory.dir("generated-snippets")) {
into "modules/api/partials/rest/actuator"
}
}
tasks.named("generateAntoraPlaybook") {
alwaysInclude = [name: "actuator-rest-api", classifier: "local-aggregate-content"]
dependsOn antoraActuatorRestApiLocalAggregateContent
}
tasks.named("antora") {
inputs.files(antoraActuatorRestApiAggregateContent)
}
artifacts {
antoraContent antoraActuatorRestApiAggregateContent
}

View File

@ -2,6 +2,8 @@ plugins {
id "dev.adamko.dokkatoo-html"
id "java"
id "org.antora"
id "org.springframework.boot.antora-contributor"
id "org.springframework.boot.antora-dependencies"
id "org.springframework.boot.deployed"
id 'org.jetbrains.kotlin.jvm'
}
@ -14,7 +16,6 @@ configurations {
remoteSpringApplicationExample
springApplicationExample
testSlices
antoraContent
all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.module.group == "org.apache.kafka" && details.requested.module.name == "kafka-server-common") {
@ -179,10 +180,6 @@ dependencies {
springApplicationExample(platform(project(":spring-boot-project:spring-boot-dependencies")))
springApplicationExample(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
antoraContent(project(path: ":spring-boot-project:spring-boot-actuator-autoconfigure", configuration: "antoraContent"))
antoraContent(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin", configuration: "antoraContent"))
antoraContent(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-maven-plugin", configuration: "antoraContent"))
testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("org.assertj:assertj-core")
@ -308,86 +305,86 @@ def getRelativeExamplesPath(var outputs) {
'example$example-output/' + fileName
}
def antoraRootAggregateContent = tasks.register("antoraRootAggregateContent", Zip) {
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "root-aggregate-content"
from("src/main") {
into "modules/ROOT/examples"
antoraDependencies {
'actuator-rest-api' {
path = ":spring-boot-project:spring-boot-actuator-autoconfigure"
source()
aggregateContent()
}
from(project.configurations.configurationProperties) {
eachFile {
it.path = rootProject
.projectDir
.toPath()
.relativize(it.file.toPath())
.toString()
.replace('\\', '/')
.replaceAll('.*/([^/]+)/build.*', 'modules/ROOT/partials/$1/spring-configuration-metadata.json')
'gradle-plugin' {
path = ":spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin"
source()
catalogContent()
}
'maven-plugin' {
path = ":spring-boot-project:spring-boot-tools:spring-boot-maven-plugin"
source()
catalogContent()
aggregateContent()
}
}
antoraContributions {
'api' {
catalogContent {
from(aggregatedJavadoc) {
into "java"
}
from(tasks.named("dokkatooGeneratePublicationHtml")) {
into "kotlin"
}
}
}
from(runRemoteSpringApplicationExample) {
into "modules/ROOT/examples"
}
from(documentDevtoolsPropertyDefaults) {
into "modules/ROOT/partials/propertydefaults"
}
from(documentStarters) {
into "modules/ROOT/partials/starters"
}
from(documentTestSlices) {
into "modules/appendix/partials/slices"
}
from(runSpringApplicationExample) {
into "modules/ROOT/partials/application"
}
from(runLoggingFormatExample) {
into "modules/ROOT/partials/logging"
}
from(documentDependencyVersionCoordinates) {
into "modules/appendix/partials/dependency-versions"
}
from(documentDependencyVersionProperties) {
into "modules/appendix/partials/dependency-versions"
}
from(documentAutoConfigurationClasses) {
into "modules/appendix/partials/auto-configuration-classes"
}
from(documentConfigurationProperties) {
into "modules/appendix/partials/configuration-properties"
}
from(tasks.getByName("generateAntoraYml")) {
into "modules"
}
}
def antoraApiCatalogContent = tasks.register("antoraApiCatalogContent", Zip) {
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "api-catalog-content"
from(aggregatedJavadoc) {
into "java"
}
from(tasks.named("dokkatooGeneratePublicationHtml")) {
into "kotlin"
}
}
def copyAntoraContentDependencies = tasks.register("copyAntoraContentDependencies", Copy) {
into layout.buildDirectory.dir('generated/docs/antora-dependencies-content')
from(configurations.antoraContent)
rename("spring-boot-actuator-autoconfigure", "spring-boot-docs")
rename("spring-boot-maven-plugin", "spring-boot-docs")
rename("spring-boot-gradle-plugin", "spring-boot-docs")
}
tasks.named("antora") {
inputs.files(antoraRootAggregateContent, antoraApiCatalogContent, copyAntoraContentDependencies)
}
gradle.projectsEvaluated {
def mavenPublication = publishing.publications.getByName("maven");
configurations.antoraContent.dependencies.forEach { dependency ->
dependency.dependencyProject.configurations.getByName(dependency.targetConfiguration)
.artifacts.forEach(mavenPublication::artifact)
'root' {
aggregateContent {
from("src/main") {
into "modules/ROOT/examples"
}
from(project.configurations.configurationProperties) {
eachFile {
it.path = rootProject
.projectDir
.toPath()
.relativize(it.file.toPath())
.toString()
.replace('\\', '/')
.replaceAll('.*/([^/]+)/build.*', 'modules/ROOT/partials/$1/spring-configuration-metadata.json')
}
}
from(runRemoteSpringApplicationExample) {
into "modules/ROOT/examples"
}
from(documentDevtoolsPropertyDefaults) {
into "modules/ROOT/partials/propertydefaults"
}
from(documentStarters) {
into "modules/ROOT/partials/starters"
}
from(documentTestSlices) {
into "modules/appendix/partials/slices"
}
from(runSpringApplicationExample) {
into "modules/ROOT/partials/application"
}
from(runLoggingFormatExample) {
into "modules/ROOT/partials/logging"
}
from(documentDependencyVersionCoordinates) {
into "modules/appendix/partials/dependency-versions"
}
from(documentDependencyVersionProperties) {
into "modules/appendix/partials/dependency-versions"
}
from(documentAutoConfigurationClasses) {
into "modules/appendix/partials/auto-configuration-classes"
}
from(documentConfigurationProperties) {
into "modules/appendix/partials/configuration-properties"
}
from(tasks.getByName("generateAntoraYml")) {
into "modules"
}
}
}
}
@ -396,12 +393,3 @@ dokkatoo {
includes.from("src/docs/dokkatoo/dokka-overview.md")
}
}
publishing {
publications {
getByName("maven") {
artifact antoraRootAggregateContent
artifact antoraApiCatalogContent
}
}
}

View File

@ -5,7 +5,7 @@ import org.gradle.plugins.ide.eclipse.model.Library
plugins {
id "java-gradle-plugin"
id "maven-publish"
id "org.antora"
id "org.springframework.boot.antora-contributor"
id "org.springframework.boot.docker-test"
id "org.springframework.boot.maven-repository"
id "org.springframework.boot.optional-dependencies"
@ -14,7 +14,6 @@ plugins {
description = "Spring Boot Gradle Plugins"
configurations {
antoraContent
"testCompileClasspath" {
// Downgrade SLF4J is required for tests to run in Eclipse
resolutionStrategy.force("org.slf4j:slf4j-api:1.7.36")
@ -166,35 +165,25 @@ javadoc {
}
}
def antoraGradlePluginLocalAggregateContent = tasks.register("antoraGradlePluginLocalAggregateContent", Zip) {
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "gradle-plugin-local-aggregate-content"
from(tasks.getByName("generateAntoraYml")) {
into "modules"
}
}
def antoraGradlePluginCatalogContent = tasks.register("antoraGradlePluginCatalogContent", Zip) {
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "gradle-plugin-catalog-content"
from(javadoc) {
into "api/java"
antoraContributions {
'gradle-plugin' {
catalogContent {
from(javadoc) {
into("api/java")
}
}
localAggregateContent {
from(tasks.named("generateAntoraYml")) {
into "modules"
}
}
source()
}
}
tasks.named("generateAntoraPlaybook") {
xrefStubs = ["appendix:.*", "api:.*", "reference:.*"]
excludeJavadocExtension = true
alwaysInclude = [name: "gradle-plugin", classifier: "local-aggregate-content"]
dependsOn antoraGradlePluginLocalAggregateContent
}
tasks.named("antora") {
inputs.files(antoraGradlePluginLocalAggregateContent, antoraGradlePluginCatalogContent)
}
artifacts {
antoraContent antoraGradlePluginCatalogContent
antoraExtensions.xref.stubs = ["appendix:.*", "api:.*", "reference:.*"]
asciidocExtensions.excludeJavadocExtension = true
}
toolchain {

View File

@ -1,5 +1,5 @@
plugins {
id "org.antora"
id "org.springframework.boot.antora-contributor"
id "org.springframework.boot.maven-plugin"
id "org.springframework.boot.optional-dependencies"
id "org.springframework.boot.docker-test"
@ -9,7 +9,6 @@ description = "Spring Boot Maven Plugin"
configurations {
dependenciesBom
antoraContent
}
dependencies {
@ -148,45 +147,30 @@ tasks.named("documentPluginGoals") {
]
}
def antoraMavenPluginLocalAggregateContent = tasks.register("antoraMavenPluginLocalAggregateContent", Zip) {
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "maven-plugin-local-aggregate-content"
from(tasks.getByName("generateAntoraYml")) {
into "modules"
}
}
def antoraMavenPluginAggregateContent = tasks.register("antoraMavenPluginAggregateContent", Zip) {
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "maven-plugin-aggregate-content"
from(documentPluginGoals) {
into "modules/maven-plugin/partials/goals"
}
}
def antoraMavenPluginCatalogContent = tasks.register("antoraMavenPluginCatalogContent", Zip) {
destinationDirectory = layout.buildDirectory.dir('generated/docs/antora-content')
archiveClassifier = "maven-plugin-catalog-content"
from(javadoc) {
into "api/java"
antoraContributions {
'maven-plugin' {
aggregateContent {
from(documentPluginGoals) {
into "modules/maven-plugin/partials/goals"
}
}
catalogContent {
from(javadoc) {
into "api/java"
}
}
localAggregateContent {
from(tasks.named("generateAntoraYml")) {
into "modules"
}
}
source()
}
}
tasks.named("generateAntoraPlaybook") {
xrefStubs = ["appendix:.*", "api:.*", "reference:.*", "how-to:.*"]
excludeJavadocExtension = true
alwaysInclude = [name: "maven-plugin", classifier: "local-aggregate-content"]
dependsOn antoraMavenPluginLocalAggregateContent
}
tasks.named("antora") {
inputs.files(antoraMavenPluginLocalAggregateContent, antoraMavenPluginAggregateContent, antoraMavenPluginCatalogContent)
}
artifacts {
antoraContent antoraMavenPluginAggregateContent
antoraContent antoraMavenPluginCatalogContent
antoraExtensions.xref.stubs = ["appendix:.*", "api:.*", "reference:.*", "how-to:.*"]
asciidocExtensions.excludeJavadocExtension = true
}
tasks.named("dockerTest").configure {