diff --git a/.bomr/bomr.yaml b/.bomr/bomr.yaml
deleted file mode 100644
index a9ac945f30f9..000000000000
--- a/.bomr/bomr.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-bomr:
- bom: spring-boot-project/spring-boot-dependencies/pom.xml
- upgrade:
- github:
- organization: spring-projects
- repository: spring-boot
- issue-labels:
- - 'type: dependency-upgrade'
- policy: same-major-version
- prohibited:
- - project: derby
- versions:
- # 10.15 requires Java 9
- - '[10.15,)'
- verify:
- ignored-dependencies:
- # Avoid conflicting transitive requirements for
- # io.grpc:grpc-core:jar:[1.0.1,1.0.1] (Jetty),
- # io.grpc:grpc-core:jar:[1.14.0,1.14.0] (Micrometer's Azure Registry), and
- # io.grpc:grpc-core:jar:[1.15.0,1.15.0] (Micrometer's Stackdriver Registry)
- - 'io.micrometer:micrometer-registry-azure-monitor'
- - 'org.eclipse.jetty.gcloud:jetty-gcloud-session-manager'
- - 'org.eclipse.jetty:jetty-home'
- repositories:
- # Caffeine Simulator's dependencies
- - 'https://maven.imagej.net/content/repositories/public/'
- # Spring Data GemFire's GemFire dependencies
- - 'https://repo.spring.io/gemstone-release-pivotal-cache'
diff --git a/.gitignore b/.gitignore
index 4dbf2657a90a..d37eb49a80be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,12 +22,16 @@ MANIFEST.MF
_site/
activemq-data
bin
+build
+!/**/src/**/bin
+!/**/src/**/build
build.log
dependency-reduced-pom.xml
dump.rdb
interpolated*.xml
lib/
manifest.yml
+out
overridedb.*
target
transaction-logs
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
deleted file mode 100644
index f432c9602236..000000000000
--- a/.mvn/jvm.config
+++ /dev/null
@@ -1 +0,0 @@
--Xmx1536m
\ No newline at end of file
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100755
index e89f07c229cb..000000000000
Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
deleted file mode 100755
index a84b7ef2d6ab..000000000000
--- a/.mvn/wrapper/maven-wrapper.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.3/maven-wrapper-0.5.3.jar
diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc
index 6c3c7c551b1d..04a4ef94b701 100755
--- a/CONTRIBUTING.adoc
+++ b/CONTRIBUTING.adoc
@@ -49,11 +49,13 @@ added after the original pull request but before a merge.
* We use the https://github.com/spring-io/spring-javaformat/[Spring JavaFormat] project
to apply code formatting conventions. If you use Eclipse and you follow the '`Importing
into eclipse`' instructions below you should get project specific formatting
- automatically. You can also install the https://github.com/spring-io/spring-javaformat/#intellij-idea[Spring JavaFormat IntelliJ Plugin]
- or format the code from the Maven build by running
- `./mvnw io.spring.javaformat:spring-javaformat-maven-plugin:apply`.
+ automatically. You can also install the
+ https://github.com/spring-io/spring-javaformat/#intellij-idea[Spring JavaFormat IntelliJ
+ Plugin] or format the code from the Gradle build by running
+ `./gradlew format`.
* The build includes checkstyle rules for many of our code conventions. Run
- `./mvnw validate` if you want to check you changes are compliant.
+ `./gradlew checkstyleMain checkstyleTest` if you want to check you changes are
+ compliant.
* Make sure all new `.java` files to have a simple Javadoc class comment with at least an
`@author` tag identifying you, and preferably at least a paragraph on what the class is
for.
@@ -75,65 +77,24 @@ added after the original pull request but before a merge.
If you don't have an IDE preference we would recommend that you use
https://spring.io/tools/sts[Spring Tools Suite] or
https://eclipse.org[Eclipse] when working with the code. We use the
-https://eclipse.org/m2e/[M2Eclipse] eclipse plugin for maven support. Other IDEs and tools
-should also work without issue.
+https://projects.eclipse.org/projects/tools.buildship[Buildship] Eclipse plugin for Gradle
+support. Other IDEs and tools should also work without issue.
=== Building from Source
-Spring Boot source can be built from the command line using
-https://maven.apache.org/run-maven/index.html[Apache Maven] on JDK 1.8 or above. We
-include '`Maven Wrapper`' scripts (`./mvnw` or `mvnw.bat`) that you can run rather than
-needing to install Maven locally.
+Spring Boot source can be built from the command line using https://gradle.org[Gradle] on
+JDK 1.8 or above. We include https://docs.gradle.org/current/userguide/gradle_wrapper.html[Gradle's
+wrapper scripts] (`./gradlew` or `gradlew.bat`) that you can run rather than needing to
+install Gradle locally.
-
-
-==== Default Build
-The project can be built from the root directory using the standard Maven command:
-
-[indent=0]
-----
- $ ./mvnw clean install
-----
-
-NOTE: You may need to increase the amount of memory available to Maven by setting
-a `MAVEN_OPTS` environment variable with the value `-Xmx512m`
-
-If you are rebuilding often, you might also want to skip the tests and the execution of
-checkstyle until you are ready to submit a pull request:
-
-[indent=0]
-----
- $ ./mvnw clean install -DskipTests -Pfast
-----
-
-
-
-==== Full Build
-You can run a full build using the following command:
+The project can be built from the root directory using the standard Gradle command:
[indent=0]
----
- $ ./mvnw -Pfull clean install
+ $ ./gradlew build
----
-NOTE: As for the standard build, you may need to increase the amount of memory available
-to Maven by setting a `MAVEN_OPTS` environment variable with the value `-Xmx512m`. We
-generate more artifacts when running the full build (such as Javadoc jars), so you may
-find the process a little slower than the standard build.
-
-[TIP]
-====
-If you want to run a build without the smoke tests and integration tests, building the
-`spring-boot-project` module is enough. You can cd there and run the same command, or you
-can run this from the top-level directory:
-
-[indent=0]
-----
- $ ./mvnw -f spring-boot-project -Pfull clean install
-----
-====
-
=== Importing into Eclipse
@@ -141,9 +102,6 @@ You can import the Spring Boot code into any Eclipse 2019-09-based distribution.
easiest way to setup a new environment is to use the Eclipse Installer with the provided
`spring-boot-project.setup` file (in the `/eclipse` folder).
-NOTE: Due to m2e issue https://bugs.eclipse.org/bugs/show_bug.cgi?id=548652[#548652] you need to be running m2e 1.14.0 or higher.
-An early milestone is available from https://download.eclipse.org/technology/m2e/milestones/1.14/.
-
==== Using the Eclipse Installer
@@ -169,9 +127,6 @@ Once complete you should find that a local workspace has been provisioned comple
all required Eclipse plugins. Projects will be grouped into working-sets to make the code
easier to navigate.
-If you want to work on the `spring-boot-gradle-plugin` you should remove the imported Maven
-project and reimport it as a Gradle project.
-
TIP: If you see import errors with `com.sun` packages make sure you have setup a valid
`JavaSE-1.8` environment. From preferences select "`Java`", "`Installed JREs`",
"`Execution Environments`" and make sure "`JavaSE-1.8`" points to a Java 1.8
@@ -179,13 +134,13 @@ install (we use AdoptOpenJDK on our CI).
-==== Manual Installation with M2Eclipse
+==== Manual Installation with Buildship
If you prefer to install Eclipse yourself you should use the
-https://eclipse.org/m2e/[M2Eclipse] eclipse plugin. If you don't already have m2eclipse
-installed it is available from the "`Eclipse marketplace`".
+https://projects.eclipse.org/projects/tools.buildship[Buildship] Eclipse plugin. If you
+don't already have Buildship installed it is available from the "`Eclipse marketplace`".
Spring Boot includes project specific source formatting settings, in order to have these
-work with m2eclipse, we provide an additional Eclipse plugin that you can install:
+work with Buildship, we provide an additional Eclipse plugin that you can install:
@@ -197,21 +152,13 @@ work with m2eclipse, we provide an additional Eclipse plugin that you can instal
NOTE: The plugin is optional. Projects can be imported without the plugins, your code
changes just won't be automatically formatted.
-With the requisite eclipse plugins installed you can select
-`import existing maven projects` from the `file` menu to import the code. You will
-need to import the root `spring-boot` pom and the `spring-boot-smoke-tests` pom separately.
+With the requisite Eclipse plugins installed you can select
+`Gradle -> Existing Gradle project` from the `File -> Import…` menu to import the code.
=== Importing into IntelliJ IDEA
-**Please, do this first!**
-Go to `Preferences | Build, Execution, Deployment | Build Tools | Maven | Importing`
-and set `VM options for importer` to `-Xmx2g` to allocate sufficient memory for IDEA's
-Maven import process to parse the Spring Boot project structure. _Not doing so could
-mean the import fails silently, leaving the project setup incomplete._
-
-For the actual import use "`File`" -> "`Open`" and select the root `pom.xml`, or the
-`spring-boot-project/pom.xml` if you only want the Spring Boot project sources.
+Use "`File`" -> "`Open`" and then select the root `build.gradle` file to import the code.
@@ -239,16 +186,8 @@ needs to be added.
=== Importing into Other IDEs
-Maven is well supported by most Java IDEs. Refer to your vendor documentation.
-
-
+Gradle is well supported by most Java IDEs. Refer to your vendor documentation.
-== Integration Tests
-The smoke tests run as part of the build when you `./mvnw install`.
-Due to the fact that they make use of the `spring-boot-maven-plugin`
-they cannot be called directly, and so instead are launched via the
-`maven-invoker-plugin`. If you encounter build failures running the integration tests,
-check the `build.log` file in the appropriate smoke test directory.
== Cloning the git repository on Windows
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000000..bc35a69d006c
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,20 @@
+plugins {
+ id 'org.jetbrains.kotlin.jvm' apply false // https://youtrack.jetbrains.com/issue/KT-30276
+}
+
+description = 'Spring Boot Build'
+
+allprojects {
+ group 'org.springframework.boot'
+
+ repositories {
+ mavenCentral()
+ maven { url 'https://repo.spring.io/milestone' }
+ maven { url 'https://repo.spring.io/snapshot' }
+ }
+
+ configurations.all {
+ resolutionStrategy.cacheChangingModulesFor 60, 'minutes'
+ }
+
+}
diff --git a/buildSrc/README.adoc b/buildSrc/README.adoc
new file mode 100644
index 000000000000..44c7bc7f4325
--- /dev/null
+++ b/buildSrc/README.adoc
@@ -0,0 +1,26 @@
+= BOM Plugin
+
+Allows one to publish a BOM from Gradle.
+Properties, dependencies, and other BOMs are applied to the `bom {}` extension.
+
+This plugin applies the `java-platform` and `maven-publish` plugins to the Project it's applied to.
+
+== Usage
+
+[source,groovy,indent=0]
+----
+plugins {
+ id 'org.springframework.boot.bom'
+}
+
+bom {
+ property 'logback.version', '1.2.3'
+ property 'junit-jupiter.version', '5.3.2'
+
+ dependency 'ch.qos.logback', 'logback-classic', '${logback.version}'
+ dependency 'ch.qos.logback', 'logback-core', '${logback.version}'
+
+ bomImport 'org.junit', 'junit-bom', '${junit-jupiter.version}'
+}
+----
+
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 000000000000..b0587d4d33e7
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,84 @@
+plugins {
+ id 'java-gradle-plugin'
+ id 'io.spring.javaformat' version '0.0.18-SNAPSHOT'
+ id 'checkstyle'
+}
+
+repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url 'https://repo.spring.io/release' }
+}
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+dependencies {
+ checkstyle 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.15'
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.0'
+ implementation 'commons-codec:commons-codec:1.13'
+ implementation 'org.apache.maven:maven-embedder:3.6.2'
+ implementation 'org.asciidoctor:asciidoctor-gradle-jvm:2.4.0'
+ implementation 'org.springframework:spring-core:5.2.2.RELEASE'
+ implementation 'org.springframework:spring-web:5.2.2.RELEASE'
+ implementation 'com.google.code.gson:gson:2.8.5'
+ implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15'
+ testImplementation 'org.assertj:assertj-core:3.11.1'
+ testImplementation 'org.apache.logging.log4j:log4j-core:2.12.1'
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2'
+}
+
+checkstyle {
+ def archive = configurations.checkstyle.filter { it.name.startsWith('spring-javaformat-checkstyle')}
+ config = resources.text.fromArchiveEntry(archive, 'io/spring/javaformat/checkstyle/checkstyle.xml')
+ toolVersion = 8.11
+}
+
+gradlePlugin {
+ plugins {
+ autoConfigurationPlugin {
+ id = "org.springframework.boot.auto-configuration"
+ implementationClass = "org.springframework.boot.build.autoconfigure.AutoConfigurationPlugin"
+ }
+ bomPlugin {
+ id = "org.springframework.boot.bom"
+ implementationClass = "org.springframework.boot.build.bom.BomPlugin"
+ }
+ configurationPropertiesPlugin {
+ id = "org.springframework.boot.configuration-properties"
+ implementationClass = "org.springframework.boot.build.context.properties.ConfigurationPropertiesPlugin"
+ }
+ conventionsPlugin {
+ id = "org.springframework.boot.conventions"
+ implementationClass = "org.springframework.boot.build.ConventionsPlugin"
+ }
+ deployedPlugin {
+ id = "org.springframework.boot.deployed"
+ implementationClass = "org.springframework.boot.build.DeployedPlugin"
+ }
+ integrationTestPlugin {
+ id = "org.springframework.boot.integration-test"
+ implementationClass = "org.springframework.boot.build.test.IntegrationTestPlugin"
+ }
+ mavenPluginPlugin {
+ id = "org.springframework.boot.maven-plugin"
+ implementationClass = "org.springframework.boot.build.mavenplugin.MavenPluginPlugin"
+ }
+ mavenRepositoryPlugin {
+ id = "org.springframework.boot.maven-repository"
+ implementationClass = "org.springframework.boot.build.MavenRepositoryPlugin"
+ }
+ optionalDependenciesPlugin {
+ id = "org.springframework.boot.optional-dependencies"
+ implementationClass = "org.springframework.boot.build.optional.OptionalDependenciesPlugin"
+ }
+ starterPlugin {
+ id = "org.springframework.boot.starter"
+ implementationClass = "org.springframework.boot.build.starters.StarterPlugin"
+ }
+ }
+}
+
+test {
+ useJUnitPlatform()
+}
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
new file mode 100644
index 000000000000..ce6d3a43daec
--- /dev/null
+++ b/buildSrc/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url 'https://repo.spring.io/snapshot' }
+ }
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == 'io.spring.javaformat') {
+ useModule "io.spring.javaformat:spring-javaformat-gradle-plugin:${requested.version}"
+ }
+ }
+ }
+}
+
+apply from: new File(settingsDir, '../gradle/build-cache-settings.gradle')
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java
new file mode 100644
index 000000000000..132940f68553
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/AsciidoctorConventions.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2019 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.net.URI;
+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.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.TaskAction;
+
+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.
+ * A task is created to resolve and unzip our documentation resources (CSS and
+ * Javascript).
+ * 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.
+ * {@link AsciidoctorTask#attributes(Map) Attributes} are configured for syntax
+ * highlighting, CSS styling, docinfo, etc.
+ *
+ * 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.
+ *
+ *
+ *
+ * @author Andy Wilkinson
+ */
+class AsciidoctorConventions {
+
+ void apply(Project project) {
+ project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> {
+ configureDocResourcesRepository(project);
+ makeAllWarningsFatal(project);
+ UnzipDocumentationResources unzipResources = createUnzipDocumentationResourcesTask(project);
+ project.getTasks().withType(AbstractAsciidoctorTask.class, (asciidoctorTask) -> {
+ configureCommonAttributes(project, asciidoctorTask);
+ configureOptions(asciidoctorTask);
+ asciidoctorTask.baseDirFollowsSourceDir();
+ Sync syncSource = createSyncDocumentationSourceTask(project, asciidoctorTask);
+ if (asciidoctorTask instanceof AsciidoctorTask) {
+ configureHtmlOnlyAttributes(project, asciidoctorTask);
+ syncSource.from(unzipResources, (resources) -> resources.into("asciidoc"));
+ asciidoctorTask.doFirst(new Action() {
+
+ @Override
+ public void execute(Task task) {
+ project.copy((spec) -> {
+ spec.from(asciidoctorTask.getSourceDir());
+ spec.into(asciidoctorTask.getOutputDir());
+ spec.include("css/**", "js/**");
+ });
+ }
+
+ });
+ }
+ });
+ });
+ }
+
+ private void configureDocResourcesRepository(Project project) {
+ project.getRepositories().maven((mavenRepo) -> {
+ mavenRepo.setUrl(URI.create("https://repo.spring.io/release"));
+ mavenRepo.mavenContent((mavenContent) -> mavenContent.includeGroup("io.spring.docresources"));
+ });
+ }
+
+ private void makeAllWarningsFatal(Project project) {
+ project.getExtensions().getByType(AsciidoctorJExtension.class).fatalWarnings(".*");
+ }
+
+ private UnzipDocumentationResources createUnzipDocumentationResourcesTask(Project project) {
+ Configuration documentationResources = project.getConfigurations().maybeCreate("documentationResources");
+ documentationResources.getDependencies()
+ .add(project.getDependencies().create("io.spring.docresources:spring-doc-resources:0.1.3.RELEASE"));
+ UnzipDocumentationResources unzipResources = project.getTasks().create("unzipDocumentationResources",
+ UnzipDocumentationResources.class);
+ unzipResources.setResources(documentationResources);
+ unzipResources.setOutputDir(new File(project.getBuildDir(), "docs/resources"));
+ return unzipResources;
+ }
+
+ 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.setSourceDir(project.relativePath(new File(syncedSource, "asciidoc/")));
+ return syncDocumentationSource;
+ }
+
+ private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) {
+ asciidoctorTask.options(Collections.singletonMap("doctype", "book"));
+ }
+
+ private void configureHtmlOnlyAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) {
+ Map attributes = new HashMap<>();
+ attributes.put("highlightjsdir", "js/highlight");
+ attributes.put("highlightjs-theme", "github");
+ attributes.put("linkcss", true);
+ attributes.put("icons", "font");
+ attributes.put("stylesheet", "css/spring.css");
+ asciidoctorTask.attributes(attributes);
+ }
+
+ private void configureCommonAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) {
+ Map attributes = new HashMap<>();
+ attributes.put("attribute-missing", "warn");
+ attributes.put("github-tag", determineGitHubTag(project));
+ attributes.put("spring-boot-artifactory-repo", determineArtifactoryRepo(project));
+ attributes.put("version", "{gradle-project-version}");
+ asciidoctorTask.attributes(attributes);
+ }
+
+ private String determineArtifactoryRepo(Project project) {
+ String version = project.getVersion().toString();
+ String type = version.substring(version.lastIndexOf('.'));
+ if (type.equals("RELEASE")) {
+ return "release";
+ }
+ if (type.startsWith("M") || type.startsWith("RC")) {
+ return "milestone";
+ }
+ return "snapshot";
+ }
+
+ private String determineGitHubTag(Project project) {
+ String version = "v" + project.getVersion();
+ return (version.endsWith("-SNAPSHOT")) ? "master" : version;
+ }
+
+ /**
+ * {@link Task} for unzipping the documentation resources.
+ */
+ public static class UnzipDocumentationResources extends DefaultTask {
+
+ private FileCollection resources;
+
+ private File outputDir;
+
+ @InputFiles
+ public FileCollection getResources() {
+ return this.resources;
+ }
+
+ public void setResources(FileCollection resources) {
+ this.resources = resources;
+ }
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return this.outputDir;
+ }
+
+ public void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ @TaskAction
+ void syncDocumentationResources() {
+ getProject().sync((copySpec) -> {
+ copySpec.into(this.outputDir);
+ for (File resource : this.resources) {
+ copySpec.from(getProject().zipTree(resource));
+ }
+ });
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java
new file mode 100644
index 000000000000..30aa02b72679
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/ConventionsPlugin.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2012-2020 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.util.Map;
+import java.util.TreeMap;
+
+import io.spring.javaformat.gradle.SpringJavaFormatPlugin;
+import org.apache.maven.artifact.repository.MavenArtifactRepository;
+import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginExtension;
+import org.gradle.api.plugins.quality.CheckstyleExtension;
+import org.gradle.api.plugins.quality.CheckstylePlugin;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.maven.MavenPom;
+import org.gradle.api.publish.maven.MavenPomDeveloperSpec;
+import org.gradle.api.publish.maven.MavenPomIssueManagement;
+import org.gradle.api.publish.maven.MavenPomLicenseSpec;
+import org.gradle.api.publish.maven.MavenPomOrganization;
+import org.gradle.api.publish.maven.MavenPomScm;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
+import org.gradle.api.tasks.bundling.Jar;
+import org.gradle.api.tasks.compile.JavaCompile;
+import org.gradle.api.tasks.javadoc.Javadoc;
+import org.gradle.api.tasks.testing.Test;
+
+/**
+ * Plugin to apply conventions to projects that are part of Spring Boot's build.
+ * Conventions are applied in response to various plugins being applied.
+ *
+ *
+ *
+ * When the {@link JavaPlugin Java plugin} is applied:
+ *
+ *
+ * {@code sourceCompatibility} is set to {@code 1.8}
+ * Spring Java Format and Checkstyle plugins are applied
+ * {@link Test} tasks are configured to use JUnit Platform and use a max heap of 1024M
+ * {@link JavaCompile} tasks are configured to use UTF-8 encoding
+ * {@link Javadoc} tasks are configured to use UTF-8 encoding
+ * {@link Jar} tasks are configured to have the following manifest entries:
+ *
+ * {@code Automatic-Module-Name}
+ * {@code Build-Jdk-Spec}
+ * {@code Built-By}
+ * {@code Implementation-Title}
+ * {@code Implementation-Version}
+ *
+ *
+ *
+ *
+ *
+ * When the {@link MavenPublishPlugin Maven Publish plugin} is applied:
+ *
+ *
+ * If the {@code deploymentRepository} property has been set, a
+ * {@link MavenArtifactRepository Maven artifact repository} is configured to publish to
+ * it.
+ * The poms of all {@link MavenPublication Maven publications} are customized to meet
+ * Maven Central's requirements.
+ * If the {@link JavaPlugin Java plugin} has also been applied, creation of Javadoc
+ * and source jars is enabled.
+ *
+ *
+ *
+ *
+ * When the {@link AsciidoctorJPlugin} is applied, the conventions in
+ * {@link AsciidoctorConventions} are applied.
+ *
+ * @author Andy Wilkinson
+ */
+public class ConventionsPlugin implements Plugin {
+
+ @Override
+ public void apply(Project project) {
+ applyJavaConventions(project);
+ applyAsciidoctorConventions(project);
+ applyMavenPublishingConventions(project);
+ }
+
+ private void applyJavaConventions(Project project) {
+ project.getPlugins().withType(JavaPlugin.class, (java) -> {
+ configureSpringJavaFormat(project);
+ project.setProperty("sourceCompatibility", "1.8");
+ project.getTasks().withType(JavaCompile.class, (compile) -> compile.getOptions().setEncoding("UTF-8"));
+ project.getTasks().withType(Javadoc.class,
+ (javadoc) -> javadoc.getOptions().source("1.8").encoding("UTF-8"));
+ project.getTasks().withType(Test.class, (test) -> {
+ test.useJUnitPlatform();
+ test.setMaxHeapSize("1024M");
+ });
+ project.getTasks().withType(Jar.class, (jar) -> {
+ project.afterEvaluate((evaluated) -> {
+ jar.manifest((manifest) -> {
+ Map attributes = new TreeMap<>();
+ attributes.put("Automatic-Module-Name", project.getName().replace("-", "."));
+ attributes.put("Build-Jdk-Spec", project.property("sourceCompatibility"));
+ attributes.put("Built-By", "Spring");
+ attributes.put("Implementation-Title", project.getDescription());
+ attributes.put("Implementation-Version", project.getVersion());
+ manifest.attributes(attributes);
+ });
+ });
+ });
+ });
+ }
+
+ private void configureSpringJavaFormat(Project project) {
+ project.getPlugins().apply(SpringJavaFormatPlugin.class);
+ project.getPlugins().apply(CheckstylePlugin.class);
+ CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
+ checkstyle.setToolVersion("8.22");
+ checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
+ String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
+ DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+ checkstyleDependencies
+ .add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
+ checkstyleDependencies
+ .add(project.getDependencies().create("io.spring.nohttp:nohttp-checkstyle:0.0.3.RELEASE"));
+ }
+
+ private void applyAsciidoctorConventions(Project project) {
+ new AsciidoctorConventions().apply(project);
+ }
+
+ private void applyMavenPublishingConventions(Project project) {
+ project.getPlugins().withType(MavenPublishPlugin.class).all((mavenPublish) -> {
+ PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
+ if (project.hasProperty("deploymentRepository")) {
+ publishing.getRepositories().maven((mavenRepository) -> {
+ mavenRepository.setUrl(project.property("deploymentRepository"));
+ mavenRepository.setName("deployment");
+ });
+ }
+ publishing.getPublications().withType(MavenPublication.class)
+ .all((mavenPublication) -> customizePom(mavenPublication.getPom(), project));
+ project.getPlugins().withType(JavaPlugin.class).all((javaPlugin) -> {
+ JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class);
+ extension.withJavadocJar();
+ extension.withSourcesJar();
+ });
+ });
+ }
+
+ private void customizePom(MavenPom pom, Project project) {
+ pom.getUrl().set("https://projects.spring.io/spring-boot/#");
+ pom.getDescription().set(project.provider(project::getDescription));
+ pom.organization(this::customizeOrganization);
+ pom.licenses(this::customizeLicences);
+ pom.developers(this::customizeDevelopers);
+ pom.scm(this::customizeScm);
+ pom.issueManagement(this::customizeIssueManagement);
+ }
+
+ private void customizeOrganization(MavenPomOrganization organization) {
+ organization.getName().set("Pivotal Software, Inc.");
+ organization.getUrl().set("https://spring.io");
+ }
+
+ private void customizeLicences(MavenPomLicenseSpec licences) {
+ licences.license((licence) -> {
+ licence.getName().set("Apache License, Version 2.0");
+ licence.getUrl().set("http://www.apache.org/licenses/LICENSE-2.0");
+ });
+ }
+
+ private void customizeDevelopers(MavenPomDeveloperSpec developers) {
+ developers.developer((developer) -> {
+ developer.getName().set("Pivotal");
+ developer.getEmail().set("info@pivotal.io");
+ developer.getOrganization().set("Pivotal Software, Inc.");
+ developer.getOrganizationUrl().set("https://www.spring.io");
+ });
+ }
+
+ private void customizeScm(MavenPomScm scm) {
+ scm.getConnection().set("scm:git:git://github.com/spring-projects/spring-boot.git");
+ scm.getDeveloperConnection().set("scm:git:ssh://git@github.com/spring-projects/spring-boot.git");
+ scm.getUrl().set("https://github.com/spring-projects/spring-boot");
+
+ }
+
+ private void customizeIssueManagement(MavenPomIssueManagement issueManagement) {
+ issueManagement.getSystem().set("GitHub");
+ issueManagement.getUrl().set("https://github.com/spring-projects/spring-boot/issues");
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/DeployedPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/DeployedPlugin.java
new file mode 100644
index 000000000000..30dc9d3db1c0
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/DeployedPlugin.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 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 org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaPlatformPlugin;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
+
+/**
+ * A plugin applied to a project that should be deployed.
+ *
+ * @author Andy Wilkinson
+ */
+public class DeployedPlugin implements Plugin {
+
+ /**
+ * Name of the task that generates the deployed pom file.
+ */
+ public static final String GENERATE_POM_TASK_NAME = "generatePomFileForMavenPublication";
+
+ @Override
+ public void apply(Project project) {
+ project.getPlugins().apply(MavenPublishPlugin.class);
+ project.getPlugins().apply(MavenRepositoryPlugin.class);
+ PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
+ MavenPublication mavenPublication = publishing.getPublications().create("maven", MavenPublication.class);
+ project.getPlugins().withType(JavaPlugin.class)
+ .all((javaPlugin) -> project.getComponents().matching((component) -> component.getName().equals("java"))
+ .all((javaComponent) -> mavenPublication.from(javaComponent)));
+ project.getPlugins().withType(JavaPlatformPlugin.class)
+ .all((javaPlugin) -> project.getComponents()
+ .matching((component) -> component.getName().equals("javaPlatform"))
+ .all((javaComponent) -> mavenPublication.from(javaComponent)));
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/MavenRepositoryPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/MavenRepositoryPlugin.java
new file mode 100644
index 000000000000..d7fcbfdcbc20
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/MavenRepositoryPlugin.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012-2020 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.HashMap;
+import java.util.Map;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.plugins.JavaLibraryPlugin;
+import org.gradle.api.plugins.JavaPlatformPlugin;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
+
+/**
+ * A plugin to make a project's {@code deployment} publication available as a Maven
+ * repository. The repository can be consumed by depending upon the project using the
+ * {@code mavenRepository} configuration.
+ *
+ * @author Andy Wilkinson
+ */
+public class MavenRepositoryPlugin implements Plugin {
+
+ /**
+ * Name of the {@code mavenRepository} configuration.
+ */
+ public static final String MAVEN_REPOSITORY_CONFIGURATION_NAME = "mavenRepository";
+
+ /**
+ * Name of the task that publishes to the project repository.
+ */
+ public static final String PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME = "publishMavenPublicationToProjectRepository";
+
+ @Override
+ public void apply(Project project) {
+ project.getPlugins().apply(MavenPublishPlugin.class);
+ PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
+ File repositoryLocation = new File(project.getBuildDir(), "maven-repository");
+ publishing.getRepositories().maven((mavenRepository) -> {
+ mavenRepository.setName("project");
+ mavenRepository.setUrl(repositoryLocation.toURI());
+ });
+ project.getTasks().matching((task) -> task.getName().equals(PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME))
+ .all((task) -> setUpProjectRepository(project, task, repositoryLocation));
+ project.getTasks().matching((task) -> task.getName().equals("publishPluginMavenPublicationToProjectRepository"))
+ .all((task) -> setUpProjectRepository(project, task, repositoryLocation));
+ }
+
+ private void setUpProjectRepository(Project project, Task publishTask, File repositoryLocation) {
+ publishTask.doFirst(new CleanAction(repositoryLocation));
+ Configuration projectRepository = project.getConfigurations().create(MAVEN_REPOSITORY_CONFIGURATION_NAME);
+ project.getArtifacts().add(projectRepository.getName(), repositoryLocation,
+ (artifact) -> artifact.builtBy(publishTask));
+ DependencySet target = projectRepository.getDependencies();
+ project.getPlugins().withType(JavaPlugin.class).all((javaPlugin) -> addMavenRepositoryDependencies(project,
+ JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, target));
+ project.getPlugins().withType(JavaLibraryPlugin.class)
+ .all((javaLibraryPlugin) -> addMavenRepositoryDependencies(project, JavaPlugin.API_CONFIGURATION_NAME,
+ target));
+ project.getPlugins().withType(JavaPlatformPlugin.class)
+ .all((javaPlugin) -> addMavenRepositoryDependencies(project, JavaPlatformPlugin.API_CONFIGURATION_NAME,
+ target));
+ }
+
+ private void addMavenRepositoryDependencies(Project project, String sourceConfigurationName, DependencySet target) {
+ project.getConfigurations().getByName(sourceConfigurationName).getDependencies()
+ .withType(ProjectDependency.class).all((dependency) -> {
+ Map dependencyDescriptor = new HashMap<>();
+ dependencyDescriptor.put("path", dependency.getDependencyProject().getPath());
+ dependencyDescriptor.put("configuration", MAVEN_REPOSITORY_CONFIGURATION_NAME);
+ target.add(project.getDependencies().project(dependencyDescriptor));
+ });
+ }
+
+ private static final class CleanAction implements Action {
+
+ private final File location;
+
+ private CleanAction(File location) {
+ this.location = location;
+ }
+
+ @Override
+ public void execute(Task task) {
+ task.getProject().delete(this.location);
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationMetadata.java
new file mode 100644
index 000000000000..c4664062b46f
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationMetadata.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012-2020 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.autoconfigure;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.TaskAction;
+
+/**
+ * A {@link Task} for generating metadata describing a project's auto-configuration
+ * classes.
+ *
+ * @author Andy Wilkinson
+ */
+public class AutoConfigurationMetadata extends AbstractTask {
+
+ private SourceSet sourceSet;
+
+ private File outputFile;
+
+ public AutoConfigurationMetadata() {
+ getInputs().file((Callable) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
+ "META-INF/spring.factories"));
+ dependsOn((Callable) () -> this.sourceSet.getProcessResourcesTaskName());
+ getProject().getConfigurations()
+ .maybeCreate(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME);
+ }
+
+ public void setSourceSet(SourceSet sourceSet) {
+ this.sourceSet = sourceSet;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return this.outputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ @TaskAction
+ void documentAutoConfiguration() throws IOException {
+ Properties autoConfiguration = readAutoConfiguration();
+ getOutputFile().getParentFile().mkdirs();
+ try (FileWriter writer = new FileWriter(getOutputFile())) {
+ autoConfiguration.store(writer, null);
+ }
+ }
+
+ private Properties readAutoConfiguration() throws IOException {
+ Properties autoConfiguration = new Properties();
+ Properties springFactories = readSpringFactories(
+ new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories"));
+ autoConfiguration.setProperty("autoConfigurationClassNames",
+ springFactories.getProperty("org.springframework.boot.autoconfigure.EnableAutoConfiguration"));
+ autoConfiguration.setProperty("module", getProject().getName());
+ return autoConfiguration;
+ }
+
+ private Properties readSpringFactories(File file) throws IOException {
+ Properties springFactories = new Properties();
+ try (Reader in = new FileReader(file)) {
+ springFactories.load(in);
+ }
+ return springFactories;
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java
new file mode 100644
index 000000000000..7c2c77b4d212
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 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.autoconfigure;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.SourceSet;
+
+import org.springframework.boot.build.DeployedPlugin;
+import org.springframework.boot.build.context.properties.ConfigurationPropertiesPlugin;
+
+/**
+ * {@link Plugin} for projects that define auto-configuration. When applied, the plugin
+ * applies the {@link DeployedPlugin}. Additionally, it reacts to the presence of the
+ * {@link JavaPlugin} by:
+ *
+ *
+ * Applying the {@link ConfigurationPropertiesPlugin}.
+ * Adding a dependency on the auto-configuration annotation processor.
+ * Defining a task that produces metadata describing the auto-configuration. The
+ * metadata is made available as an artifact in the
+ *
+ *
+ * @author Andy Wilkinson
+ */
+public class AutoConfigurationPlugin implements Plugin {
+
+ /**
+ * Name of the {@link Configuration} that holds the auto-configuration metadata
+ * artifact.
+ */
+ public static final String AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME = "autoConfigurationMetadata";
+
+ @Override
+ public void apply(Project project) {
+ project.getPlugins().apply(DeployedPlugin.class);
+ project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
+ project.getPlugins().apply(ConfigurationPropertiesPlugin.class);
+ Configuration annotationProcessors = project.getConfigurations()
+ .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
+ annotationProcessors.getDependencies()
+ .add(project.getDependencies().project(Collections.singletonMap("path",
+ ":spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-processor")));
+ annotationProcessors.getDependencies()
+ .add(project.getDependencies().project(Collections.singletonMap("path",
+ ":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor")));
+ project.getTasks().create("autoConfigurationMetadata", AutoConfigurationMetadata.class, (task) -> {
+ task.setSourceSet(project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
+ .getByName(SourceSet.MAIN_SOURCE_SET_NAME));
+ task.setOutputFile(new File(project.getBuildDir(), "auto-configuration-metadata.properties"));
+ project.getArtifacts().add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME,
+ project.provider((Callable) task::getOutputFile), (artifact) -> artifact.builtBy(task));
+ });
+ });
+ }
+
+}
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
new file mode 100644
index 000000000000..6302342dd756
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/DocumentAutoConfigurationClasses.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2012-2020 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.autoconfigure;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.Properties;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Task} used to document auto-configuration classes.
+ *
+ * @author Andy Wilkinson
+ */
+public class DocumentAutoConfigurationClasses extends AbstractTask {
+
+ private FileCollection autoConfiguration;
+
+ private File outputDir;
+
+ @InputFiles
+ public FileCollection getAutoConfiguration() {
+ return this.autoConfiguration;
+ }
+
+ public void setAutoConfiguration(FileCollection autoConfiguration) {
+ this.autoConfiguration = autoConfiguration;
+ }
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return this.outputDir;
+ }
+
+ public void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ @TaskAction
+ void documentAutoConfigurationClasses() throws IOException {
+ for (File metadataFile : this.autoConfiguration) {
+ Properties metadata = new Properties();
+ try (Reader reader = new FileReader(metadataFile)) {
+ metadata.load(reader);
+ }
+ AutoConfiguration autoConfiguration = new AutoConfiguration(metadata.getProperty("module"), new TreeSet<>(
+ StringUtils.commaDelimitedListToSet(metadata.getProperty("autoConfigurationClassNames"))));
+ writeTable(autoConfiguration);
+ }
+ }
+
+ private void writeTable(AutoConfiguration autoConfigurationClasses) throws IOException {
+ this.outputDir.mkdirs();
+ try (PrintWriter writer = new PrintWriter(
+ new FileWriter(new File(this.outputDir, autoConfigurationClasses.module + ".adoc")))) {
+ writer.println("[cols=\"4,1\"]");
+ writer.println("|===");
+ writer.println("| Configuration Class | Links");
+
+ for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses.classes) {
+ writer.println();
+ writer.printf("| {spring-boot-code}/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.println("|===");
+ }
+ }
+
+ private static final class AutoConfiguration {
+
+ private final String module;
+
+ private final SortedSet classes;
+
+ private AutoConfiguration(String module, Set classNames) {
+ this.module = module;
+ this.classes = classNames.stream().map((className) -> {
+ String path = className.replace('.', '/');
+ String name = className.substring(className.lastIndexOf('.') + 1);
+ return new AutoConfigurationClass(name, path);
+ }).collect(Collectors.toCollection(TreeSet::new));
+ }
+
+ }
+
+ private static final class AutoConfigurationClass implements Comparable {
+
+ private final String name;
+
+ private final String path;
+
+ private AutoConfigurationClass(String name, String path) {
+ this.name = name;
+ this.path = path;
+ }
+
+ @Override
+ public int compareTo(AutoConfigurationClass other) {
+ return this.name.compareTo(other.name);
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java
new file mode 100644
index 000000000000..e524749a0da6
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyObjectSupport;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.gradle.api.InvalidUserCodeException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.util.ConfigureUtil;
+
+import org.springframework.boot.build.bom.Library.Exclusion;
+import org.springframework.boot.build.bom.Library.Group;
+import org.springframework.boot.build.bom.Library.Module;
+import org.springframework.boot.build.bom.Library.ProhibitedVersion;
+import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
+
+/**
+ * DSL extensions for {@link BomPlugin}.
+ *
+ * @author Andy Wilkinson
+ */
+public class BomExtension {
+
+ private final Map properties = new LinkedHashMap<>();
+
+ private final Map artifactVersionProperties = new HashMap<>();
+
+ private final List libraries = new ArrayList();
+
+ private final DependencyHandler dependencyHandler;
+
+ private final UpgradeHandler upgradeHandler = new UpgradeHandler();
+
+ public BomExtension(DependencyHandler dependencyHandler) {
+ this.dependencyHandler = dependencyHandler;
+ }
+
+ public List getLibraries() {
+ return this.libraries;
+ }
+
+ public void upgrade(Closure> closure) {
+ ConfigureUtil.configure(closure, this.upgradeHandler);
+ }
+
+ public Upgrade getUpgrade() {
+ return new Upgrade(this.upgradeHandler.upgradePolicy, new GitHub(this.upgradeHandler.gitHub.organization,
+ this.upgradeHandler.gitHub.repository, this.upgradeHandler.gitHub.issueLabels));
+ }
+
+ public void library(String name, String version, Closure> closure) {
+ LibraryHandler libraryHandler = new LibraryHandler();
+ ConfigureUtil.configure(closure, libraryHandler);
+ addLibrary(new Library(name, DependencyVersion.parse(version), libraryHandler.groups,
+ libraryHandler.prohibitedVersions));
+ }
+
+ private String createDependencyNotation(String groupId, String artifactId, DependencyVersion version) {
+ return groupId + ":" + artifactId + ":" + version;
+ }
+
+ Map getProperties() {
+ return this.properties;
+ }
+
+ String getArtifactVersionProperty(String groupId, String artifactId) {
+ String coordinates = groupId + ":" + artifactId;
+ return this.artifactVersionProperties.get(coordinates);
+ }
+
+ private void putArtifactVersionProperty(String groupId, String artifactId, String versionProperty) {
+ String coordinates = groupId + ":" + artifactId;
+ String existing = this.artifactVersionProperties.putIfAbsent(coordinates, versionProperty);
+ if (existing != null) {
+ throw new InvalidUserDataException("Cannot put version property for '" + coordinates
+ + "'. Version property '" + existing + "' has already been stored.");
+ }
+ }
+
+ private void addLibrary(Library library) {
+ this.libraries.add(library);
+ this.properties.put(library.getVersionProperty(), library.getVersion());
+ for (Group group : library.getGroups()) {
+ for (Module module : group.getModules()) {
+ this.putArtifactVersionProperty(group.getId(), module.getName(), library.getVersionProperty());
+ this.dependencyHandler.getConstraints().add("api",
+ createDependencyNotation(group.getId(), module.getName(), library.getVersion()));
+ }
+ for (String bomImport : group.getBoms()) {
+ this.putArtifactVersionProperty(group.getId(), bomImport, library.getVersionProperty());
+ this.dependencyHandler.add("api", this.dependencyHandler
+ .enforcedPlatform(createDependencyNotation(group.getId(), bomImport, library.getVersion())));
+ }
+ }
+ }
+
+ public static class LibraryHandler {
+
+ private final List groups = new ArrayList<>();
+
+ private final List prohibitedVersions = new ArrayList<>();
+
+ public void group(String id, Closure> closure) {
+ GroupHandler groupHandler = new GroupHandler(id);
+ ConfigureUtil.configure(closure, groupHandler);
+ this.groups
+ .add(new Group(groupHandler.id, groupHandler.modules, groupHandler.plugins, groupHandler.imports));
+ }
+
+ public void prohibit(String range, Closure> closure) {
+ ProhibitedVersionHandler prohibitedVersionHandler = new ProhibitedVersionHandler();
+ ConfigureUtil.configure(closure, prohibitedVersionHandler);
+ try {
+ this.prohibitedVersions.add(new ProhibitedVersion(VersionRange.createFromVersionSpec(range),
+ prohibitedVersionHandler.reason));
+ }
+ catch (InvalidVersionSpecificationException ex) {
+ throw new InvalidUserCodeException("Invalid version range", ex);
+ }
+ }
+
+ public static class ProhibitedVersionHandler {
+
+ private String reason;
+
+ public void because(String because) {
+ this.reason = because;
+ }
+
+ }
+
+ public class GroupHandler extends GroovyObjectSupport {
+
+ private final String id;
+
+ private List modules = new ArrayList<>();
+
+ private List imports = new ArrayList<>();
+
+ private List plugins = new ArrayList<>();
+
+ public GroupHandler(String id) {
+ this.id = id;
+ }
+
+ public void setModules(List modules) {
+ this.modules = modules.stream()
+ .map((input) -> (input instanceof Module) ? (Module) input : new Module((String) input))
+ .collect(Collectors.toList());
+ }
+
+ public void setImports(List imports) {
+ this.imports = imports;
+ }
+
+ public void setPlugins(List plugins) {
+ this.plugins = plugins;
+ }
+
+ public Object methodMissing(String name, Object args) {
+ if (args instanceof Object[] && ((Object[]) args).length == 1) {
+ Object arg = ((Object[]) args)[0];
+ if (arg instanceof Closure) {
+ ExclusionHandler exclusionHandler = new ExclusionHandler();
+ ConfigureUtil.configure((Closure>) arg, exclusionHandler);
+ return new Module(name, exclusionHandler.exclusions);
+ }
+ }
+ throw new InvalidUserDataException("Invalid exclusion configuration for module '" + name + "'");
+ }
+
+ public class ExclusionHandler {
+
+ private final List exclusions = new ArrayList<>();
+
+ public void exclude(Map exclusion) {
+ this.exclusions.add(new Exclusion(exclusion.get("group"), exclusion.get("module")));
+ }
+
+ }
+
+ }
+
+ }
+
+ public static class UpgradeHandler {
+
+ private UpgradePolicy upgradePolicy;
+
+ private final GitHubHandler gitHub = new GitHubHandler();
+
+ public void setPolicy(UpgradePolicy upgradePolicy) {
+ this.upgradePolicy = upgradePolicy;
+ }
+
+ public void gitHub(Closure> closure) {
+ ConfigureUtil.configure(closure, this.gitHub);
+ }
+
+ }
+
+ public static final class Upgrade {
+
+ private final UpgradePolicy upgradePolicy;
+
+ private final GitHub gitHub;
+
+ private Upgrade(UpgradePolicy upgradePolicy, GitHub gitHub) {
+ this.upgradePolicy = upgradePolicy;
+ this.gitHub = gitHub;
+ }
+
+ public UpgradePolicy getPolicy() {
+ return this.upgradePolicy;
+ }
+
+ public GitHub getGitHub() {
+ return this.gitHub;
+ }
+
+ }
+
+ public static class GitHubHandler {
+
+ private String organization = "spring-projects";
+
+ private String repository = "spring-boot";
+
+ private List issueLabels;
+
+ public void setOrganization(String organization) {
+ this.organization = organization;
+ }
+
+ public void setRepository(String repository) {
+ this.repository = repository;
+ }
+
+ public void setIssueLabels(List issueLabels) {
+ this.issueLabels = issueLabels;
+ }
+
+ }
+
+ public static final class GitHub {
+
+ private String organization = "spring-projects";
+
+ private String repository = "spring-boot";
+
+ private List issueLabels;
+
+ private GitHub(String organization, String repository, List issueLabels) {
+ this.organization = organization;
+ this.repository = repository;
+ this.issueLabels = issueLabels;
+ }
+
+ public String getOrganization() {
+ return this.organization;
+ }
+
+ public String getRepository() {
+ return this.repository;
+ }
+
+ public List getIssueLabels() {
+ return this.issueLabels;
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java
new file mode 100644
index 000000000000..9b08ede52d1e
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomPlugin.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import groovy.util.Node;
+import groovy.xml.QName;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.plugins.JavaPlatformExtension;
+import org.gradle.api.plugins.JavaPlatformPlugin;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.maven.MavenPom;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.tasks.GenerateMavenPom;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+
+import org.springframework.boot.build.DeployedPlugin;
+import org.springframework.boot.build.MavenRepositoryPlugin;
+import org.springframework.boot.build.bom.Library.Group;
+import org.springframework.boot.build.bom.bomr.UpgradeBom;
+import org.springframework.boot.build.mavenplugin.MavenExec;
+import org.springframework.util.FileCopyUtils;
+
+/**
+ * {@link Plugin} for defining a bom. Dependencies are added as constraints in the
+ * {@code api} configuration. Imported boms are added as enforced platforms in the
+ * {@code api} configuration.
+ *
+ * @author Andy Wilkinson
+ */
+public class BomPlugin implements Plugin {
+
+ @Override
+ public void apply(Project project) {
+ PluginContainer plugins = project.getPlugins();
+ plugins.apply(DeployedPlugin.class);
+ plugins.apply(MavenRepositoryPlugin.class);
+ plugins.apply(JavaPlatformPlugin.class);
+ JavaPlatformExtension javaPlatform = project.getExtensions().getByType(JavaPlatformExtension.class);
+ javaPlatform.allowDependencies();
+ BomExtension bom = project.getExtensions().create("bom", BomExtension.class, project.getDependencies());
+ project.getTasks().create("bomrCheck", CheckBom.class, bom);
+ project.getTasks().create("bomrUpgrade", UpgradeBom.class, bom);
+ new PublishingCustomizer(project, bom).customize();
+ Configuration effectiveBomConfiguration = project.getConfigurations().create("effectiveBom");
+ project.getTasks().matching((task) -> task.getName().equals(DeployedPlugin.GENERATE_POM_TASK_NAME))
+ .all((task) -> {
+ Sync syncBom = project.getTasks().create("syncBom", Sync.class);
+ syncBom.dependsOn(task);
+ File generatedBomDir = new File(project.getBuildDir(), "generated/bom");
+ syncBom.setDestinationDir(generatedBomDir);
+ syncBom.from(((GenerateMavenPom) task).getDestination(), (pom) -> pom.rename((name) -> "pom.xml"));
+ try {
+ String settingsXmlContent = FileCopyUtils
+ .copyToString(new InputStreamReader(
+ getClass().getClassLoader().getResourceAsStream("effective-bom-settings.xml"),
+ StandardCharsets.UTF_8))
+ .replace("localRepositoryPath",
+ new File(project.getBuildDir(), "local-m2-repository").getAbsolutePath());
+ syncBom.from(project.getResources().getText().fromString(settingsXmlContent),
+ (settingsXml) -> settingsXml.rename((name) -> "settings.xml"));
+ }
+ catch (IOException ex) {
+ throw new GradleException("Failed to prepare settings.xml", ex);
+ }
+ MavenExec generateEffectiveBom = project.getTasks().create("generateEffectiveBom", MavenExec.class);
+ generateEffectiveBom.setProjectDir(generatedBomDir);
+ File effectiveBom = new File(project.getBuildDir(),
+ "generated/effective-bom/" + project.getName() + "-effective-bom.xml");
+ generateEffectiveBom.args("--settings", "settings.xml", "help:effective-pom",
+ "-Doutput=" + effectiveBom);
+ generateEffectiveBom.dependsOn(syncBom);
+ generateEffectiveBom.getOutputs().file(effectiveBom);
+ generateEffectiveBom.doLast(new StripUnrepeatableOutputAction(effectiveBom));
+ project.getArtifacts().add(effectiveBomConfiguration.getName(), effectiveBom,
+ (artifact) -> artifact.builtBy(generateEffectiveBom));
+ });
+ }
+
+ private static final class PublishingCustomizer {
+
+ private final Project project;
+
+ private final BomExtension bom;
+
+ private PublishingCustomizer(Project project, BomExtension bom) {
+ this.project = project;
+ this.bom = bom;
+ }
+
+ private void customize() {
+ PublishingExtension publishing = this.project.getExtensions().getByType(PublishingExtension.class);
+ publishing.getPublications().withType(MavenPublication.class).all(this::configurePublication);
+ }
+
+ private void configurePublication(MavenPublication publication) {
+ publication.pom(this::customizePom);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void customizePom(MavenPom pom) {
+ pom.withXml((xml) -> {
+ Node projectNode = xml.asNode();
+ Node properties = new Node(null, "properties");
+ this.bom.getProperties().forEach(properties::appendNode);
+ Node dependencyManagement = findChild(projectNode, "dependencyManagement");
+ if (dependencyManagement != null) {
+ addPropertiesBeforeDependencyManagement(projectNode, properties);
+ replaceVersionsWithVersionPropertyReferences(dependencyManagement);
+ addExclusionsToManagedDependencies(dependencyManagement);
+ }
+ else {
+ projectNode.children().add(properties);
+ }
+ addPluginManagement(projectNode);
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addPropertiesBeforeDependencyManagement(Node projectNode, Node properties) {
+ for (int i = 0; i < projectNode.children().size(); i++) {
+ if (isNodeWithName(projectNode.children().get(i), "dependencyManagement")) {
+ projectNode.children().add(i, properties);
+ break;
+ }
+ }
+ }
+
+ private void replaceVersionsWithVersionPropertyReferences(Node dependencyManagement) {
+ Node dependencies = findChild(dependencyManagement, "dependencies");
+ if (dependencies != null) {
+ for (Node dependency : findChildren(dependencies, "dependency")) {
+ String groupId = findChild(dependency, "groupId").text();
+ String artifactId = findChild(dependency, "artifactId").text();
+ findChild(dependency, "version")
+ .setValue("${" + this.bom.getArtifactVersionProperty(groupId, artifactId) + "}");
+ }
+ }
+ }
+
+ private void addExclusionsToManagedDependencies(Node dependencyManagement) {
+ Node dependencies = findChild(dependencyManagement, "dependencies");
+ if (dependencies != null) {
+ for (Node dependency : findChildren(dependencies, "dependency")) {
+ String groupId = findChild(dependency, "groupId").text();
+ String artifactId = findChild(dependency, "artifactId").text();
+ this.bom.getLibraries().stream().flatMap((library) -> library.getGroups().stream())
+ .filter((group) -> group.getId().equals(groupId))
+ .flatMap((group) -> group.getModules().stream())
+ .filter((module) -> module.getName().equals(artifactId))
+ .flatMap((module) -> module.getExclusions().stream()).forEach((exclusion) -> {
+ Node exclusions = findOrCreateNode(dependency, "exclusions");
+ Node node = new Node(exclusions, "exclusion");
+ node.appendNode("groupId", exclusion.getGroupId());
+ node.appendNode("artifactId", exclusion.getArtifactId());
+ });
+ }
+ }
+ }
+
+ private void addPluginManagement(Node projectNode) {
+ for (Library library : this.bom.getLibraries()) {
+ for (Group group : library.getGroups()) {
+ Node plugins = findOrCreateNode(projectNode, "build", "pluginManagement", "plugins");
+ for (String pluginName : group.getPlugins()) {
+ Node plugin = new Node(plugins, "plugin");
+ plugin.appendNode("groupId", group.getId());
+ plugin.appendNode("artifactId", pluginName);
+ plugin.appendNode("version", "${" + library.getVersionProperty() + "}");
+ }
+ }
+ }
+ }
+
+ private Node findOrCreateNode(Node parent, String... path) {
+ Node current = parent;
+ for (String nodeName : path) {
+ Node child = findChild(current, nodeName);
+ if (child == null) {
+ child = new Node(current, nodeName);
+ }
+ current = child;
+ }
+ return current;
+ }
+
+ private Node findChild(Node parent, String name) {
+ for (Object child : parent.children()) {
+ if (child instanceof Node) {
+ Node node = (Node) child;
+ if ((node.name() instanceof QName) && name.equals(((QName) node.name()).getLocalPart())) {
+ return node;
+ }
+ if (name.equals(node.name())) {
+ return node;
+ }
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private List findChildren(Node parent, String name) {
+ return (List) parent.children().stream().filter((child) -> isNodeWithName(child, name))
+ .collect(Collectors.toList());
+
+ }
+
+ private boolean isNodeWithName(Object candidate, String name) {
+ if (candidate instanceof Node) {
+ Node node = (Node) candidate;
+ if ((node.name() instanceof QName) && name.equals(((QName) node.name()).getLocalPart())) {
+ return true;
+ }
+ if (name.equals(node.name())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ }
+
+ private static final class StripUnrepeatableOutputAction implements Action {
+
+ private final File effectiveBom;
+
+ private StripUnrepeatableOutputAction(File xmlFile) {
+ this.effectiveBom = xmlFile;
+ }
+
+ @Override
+ public void execute(Task task) {
+ try {
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(this.effectiveBom);
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ NodeList comments = (NodeList) xpath.evaluate("//comment()", document, XPathConstants.NODESET);
+ for (int i = 0; i < comments.getLength(); i++) {
+ org.w3c.dom.Node comment = comments.item(i);
+ comment.getParentNode().removeChild(comment);
+ }
+ org.w3c.dom.Node build = (org.w3c.dom.Node) xpath.evaluate("/project/build", document,
+ XPathConstants.NODE);
+ build.getParentNode().removeChild(build);
+ org.w3c.dom.Node reporting = (org.w3c.dom.Node) xpath.evaluate("/project/reporting", document,
+ XPathConstants.NODE);
+ reporting.getParentNode().removeChild(reporting);
+ TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document),
+ new StreamResult(this.effectiveBom));
+ }
+ catch (Exception ex) {
+ throw new TaskExecutionException(task, ex);
+ }
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java
new file mode 100644
index 000000000000..cde5d53513b2
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom;
+
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.tasks.TaskAction;
+
+import org.springframework.boot.build.bom.Library.Group;
+import org.springframework.boot.build.bom.Library.Module;
+import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
+
+/**
+ * Checks the validity of a bom.
+ *
+ * @author Andy Wilkinson
+ */
+public class CheckBom extends DefaultTask {
+
+ private final BomExtension bom;
+
+ @Inject
+ public CheckBom(BomExtension bom) {
+ this.bom = bom;
+ }
+
+ @TaskAction
+ void checkBom() {
+ for (Library library : this.bom.getLibraries()) {
+ for (Group group : library.getGroups()) {
+ for (Module module : group.getModules()) {
+ if (!module.getExclusions().isEmpty()) {
+ checkExclusions(group.getId(), module, library.getVersion());
+ }
+ }
+ }
+ }
+ }
+
+ private void checkExclusions(String groupId, Module module, DependencyVersion version) {
+ Set resolved = getProject().getConfigurations()
+ .detachedConfiguration(
+ getProject().getDependencies().create(groupId + ":" + module.getName() + ":" + version))
+ .getResolvedConfiguration().getResolvedArtifacts().stream()
+ .map((artifact) -> artifact.getModuleVersion().getId())
+ .map((id) -> id.getGroup() + ":" + id.getModule().getName()).collect(Collectors.toSet());
+ Set exclusions = module.getExclusions().stream()
+ .map((exclusion) -> exclusion.getGroupId() + ":" + exclusion.getArtifactId())
+ .collect(Collectors.toSet());
+ Set unused = new TreeSet<>();
+ for (String exclusion : exclusions) {
+ if (!resolved.contains(exclusion) && exclusion.endsWith(":*")) {
+ String group = exclusion.substring(0, exclusion.indexOf(':') + 1);
+ if (!resolved.stream().filter((candidate) -> candidate.startsWith(group)).findFirst().isPresent()) {
+ unused.add(exclusion);
+ }
+ }
+ }
+ exclusions.removeAll(resolved);
+ if (!unused.isEmpty()) {
+ throw new InvalidUserDataException(
+ "Unnecessary exclusions on " + groupId + ":" + module.getName() + ": " + exclusions);
+ }
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java
new file mode 100644
index 000000000000..e28447838ca0
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.maven.artifact.versioning.VersionRange;
+
+import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
+
+/**
+ * A collection of modules, Maven plugins, and Maven boms that are versioned and released
+ * together.
+ *
+ * @author Andy Wilkinson
+ */
+public class Library {
+
+ private final String name;
+
+ private final DependencyVersion version;
+
+ private final List groups;
+
+ private final String versionProperty;
+
+ private final List prohibitedVersions;
+
+ /**
+ * Create a new {@code Library} with the given {@code name}, {@code version}, and
+ * {@code groups}.
+ * @param name name of the library
+ * @param version version of the library
+ * @param groups groups in the library
+ * @param prohibitedVersions version of the library that are prohibited
+ */
+ public Library(String name, DependencyVersion version, List groups,
+ List prohibitedVersions) {
+ this.name = name;
+ this.version = version;
+ this.groups = groups;
+ this.versionProperty = name.toLowerCase(Locale.ENGLISH).replace(' ', '-') + ".version";
+ this.prohibitedVersions = prohibitedVersions;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public DependencyVersion getVersion() {
+ return this.version;
+ }
+
+ public List getGroups() {
+ return this.groups;
+ }
+
+ public String getVersionProperty() {
+ return this.versionProperty;
+ }
+
+ public List getProhibitedVersions() {
+ return this.prohibitedVersions;
+ }
+
+ /**
+ * A version or range of versions that are prohibited from being used in a bom.
+ */
+ public static class ProhibitedVersion {
+
+ private final VersionRange range;
+
+ private final String reason;
+
+ public ProhibitedVersion(VersionRange range, String reason) {
+ this.range = range;
+ this.reason = reason;
+ }
+
+ public VersionRange getRange() {
+ return this.range;
+ }
+
+ public String getReason() {
+ return this.reason;
+ }
+
+ }
+
+ /**
+ * A collection of modules, Maven plugins, and Maven boms with the same group ID.
+ */
+ public static class Group {
+
+ private final String id;
+
+ private final List modules;
+
+ private final List plugins;
+
+ private final List boms;
+
+ public Group(String id, List modules, List plugins, List boms) {
+ this.id = id;
+ this.modules = modules;
+ this.plugins = plugins;
+ this.boms = boms;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public List getModules() {
+ return this.modules;
+ }
+
+ public List getPlugins() {
+ return this.plugins;
+ }
+
+ public List getBoms() {
+ return this.boms;
+ }
+
+ }
+
+ /**
+ * A module in a group.
+ */
+ public static class Module {
+
+ private final String name;
+
+ private final List exclusions;
+
+ public Module(String name) {
+ this(name, Collections.emptyList());
+ }
+
+ public Module(String name, List exclusions) {
+ this.name = name;
+ this.exclusions = exclusions;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public List getExclusions() {
+ return this.exclusions;
+ }
+
+ }
+
+ /**
+ * An exclusion of a dependency identified by its group ID and artifact ID.
+ */
+ public static class Exclusion {
+
+ private final String groupId;
+
+ private final String artifactId;
+
+ public Exclusion(String groupId, String artifactId) {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ }
+
+ public String getGroupId() {
+ return this.groupId;
+ }
+
+ public String getArtifactId() {
+ return this.artifactId;
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/UpgradePolicy.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/UpgradePolicy.java
new file mode 100644
index 000000000000..831d3b4f85cb
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/UpgradePolicy.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom;
+
+import java.util.function.BiPredicate;
+
+import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
+
+/**
+ * Policies used to decide which versions are considered as possible upgrades.
+ *
+ * @author Andy Wilkinson
+ */
+public enum UpgradePolicy implements BiPredicate {
+
+ /**
+ * All versions more recent than the current version will be suggested as possible
+ * upgrades.
+ */
+ ANY((candidate, current) -> current.compareTo(candidate) < 0),
+
+ /**
+ * New minor versions of the current major version will be suggested as possible
+ * upgrades. For example, if the current version is 1.2.3, all 1.x.y versions after
+ * 1.2.3 will be suggested. 2.x versions will not be offered.
+ */
+ SAME_MAJOR_VERSION(DependencyVersion::isSameMajorAndNewerThan),
+
+ /**
+ * New patch versions of the current minor version will be offered as possible
+ * upgrades. For example, if the current version is 1.2.3, all 1.2.x versions after
+ * 1.2.3 will be suggested. 1.x versions will not be offered.
+ */
+ SAME_MINOR_VERSION(DependencyVersion::isSameMinorAndNewerThan);
+
+ private BiPredicate delegate;
+
+ UpgradePolicy(BiPredicate delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean test(DependencyVersion candidate, DependencyVersion current) {
+ return this.delegate.test(candidate, current);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java
new file mode 100644
index 000000000000..b216e9e38293
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.gradle.api.internal.tasks.userinput.UserInputHandler;
+
+import org.springframework.boot.build.bom.Library;
+import org.springframework.boot.build.bom.Library.Group;
+import org.springframework.boot.build.bom.Library.Module;
+import org.springframework.boot.build.bom.Library.ProhibitedVersion;
+import org.springframework.boot.build.bom.UpgradePolicy;
+import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
+import org.springframework.util.StringUtils;
+
+/**
+ * Interactive {@link UpgradeResolver} that uses command line input to choose the upgrades
+ * to apply.
+ *
+ * @author Andy Wilkinson
+ */
+public final class InteractiveUpgradeResolver implements UpgradeResolver {
+
+ private final VersionResolver versionResolver;
+
+ private final UpgradePolicy upgradePolicy;
+
+ private final UserInputHandler userInputHandler;
+
+ InteractiveUpgradeResolver(VersionResolver versionResolver, UpgradePolicy upgradePolicy,
+ UserInputHandler userInputHandler) {
+ this.versionResolver = versionResolver;
+ this.upgradePolicy = upgradePolicy;
+ this.userInputHandler = userInputHandler;
+ }
+
+ @Override
+ public List resolveUpgrades(Collection libraries) {
+ return libraries.stream().map(this::resolveUpgrade).filter((upgrade) -> upgrade != null)
+ .collect(Collectors.toList());
+ }
+
+ private Upgrade resolveUpgrade(Library library) {
+ Map> moduleVersions = new LinkedHashMap<>();
+ for (Group group : library.getGroups()) {
+ for (Module module : group.getModules()) {
+ moduleVersions.put(group.getId() + ":" + module.getName(),
+ getLaterVersionsForModule(group.getId(), module.getName(), library.getVersion()));
+ }
+ }
+ List allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct()
+ .filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
+ .collect(Collectors.toList());
+ if (allVersions.isEmpty()) {
+ return null;
+ }
+ List versionOptions = allVersions.stream()
+ .map((version) -> new VersionOption(version, getMissingModules(moduleVersions, version)))
+ .collect(Collectors.toList());
+ VersionOption current = new VersionOption(library.getVersion(), Collections.emptyList());
+ VersionOption selected = this.userInputHandler.selectOption(library.getName() + " " + library.getVersion(),
+ versionOptions, current);
+ return (selected.equals(current)) ? null : new Upgrade(library, selected.version);
+ }
+
+ private boolean isPermitted(DependencyVersion dependencyVersion, List prohibitedVersions) {
+ if (prohibitedVersions.isEmpty()) {
+ return true;
+ }
+ for (ProhibitedVersion prohibitedVersion : prohibitedVersions) {
+ if (prohibitedVersion.getRange()
+ .containsVersion(new DefaultArtifactVersion(dependencyVersion.toString()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private List getMissingModules(Map> moduleVersions,
+ DependencyVersion version) {
+ List missingModules = new ArrayList<>();
+ moduleVersions.forEach((name, versions) -> {
+ if (!versions.contains(version)) {
+ missingModules.add(name);
+ }
+ });
+ return missingModules;
+ }
+
+ private SortedSet getLaterVersionsForModule(String groupId, String artifactId,
+ DependencyVersion currentVersion) {
+ SortedSet versions = this.versionResolver.resolveVersions(groupId, artifactId);
+ versions.removeIf((candidate) -> !this.upgradePolicy.test(candidate, currentVersion));
+ return versions;
+ }
+
+ private static final class VersionOption {
+
+ private final DependencyVersion version;
+
+ private final List missingModules;
+
+ private VersionOption(DependencyVersion version, List missingModules) {
+ this.version = version;
+ this.missingModules = missingModules;
+ }
+
+ @Override
+ public String toString() {
+ if (this.missingModules.isEmpty()) {
+ return this.version.toString();
+ }
+ return this.version + " (some modules are missing: "
+ + StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")";
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MavenMetadataVersionResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MavenMetadataVersionResolver.java
new file mode 100644
index 000000000000..d29f6b075937
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/MavenMetadataVersionResolver.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr;
+
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * A {@link VersionResolver} that examines {@code maven-metadata.xml} to determine the
+ * available versions.
+ *
+ * @author Andy Wilkinson
+ */
+final class MavenMetadataVersionResolver implements VersionResolver {
+
+ private final RestTemplate rest;
+
+ private final List repositoryUrls;
+
+ MavenMetadataVersionResolver(List repositoryUrls) {
+ this(new RestTemplate(Arrays.asList(new StringHttpMessageConverter())), repositoryUrls);
+ }
+
+ MavenMetadataVersionResolver(RestTemplate restTemplate, List repositoryUrls) {
+ this.rest = restTemplate;
+ this.repositoryUrls = repositoryUrls;
+ }
+
+ @Override
+ public SortedSet resolveVersions(String groupId, String artifactId) {
+ Set versions = new HashSet();
+ for (String repositoryUrl : this.repositoryUrls) {
+ versions.addAll(resolveVersions(groupId, artifactId, repositoryUrl));
+ }
+ return new TreeSet<>(versions.stream().map(DependencyVersion::parse).collect(Collectors.toSet()));
+ }
+
+ private Set resolveVersions(String groupId, String artifactId, String repositoryUrl) {
+ Set versions = new HashSet();
+ String url = repositoryUrl + "/" + groupId.replace('.', '/') + "/" + artifactId + "/maven-metadata.xml";
+ try {
+ String metadata = this.rest.getForObject(url, String.class);
+ Document metadataDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+ .parse(new InputSource(new StringReader(metadata)));
+ NodeList versionNodes = (NodeList) XPathFactory.newInstance().newXPath()
+ .evaluate("/metadata/versioning/versions/version", metadataDocument, XPathConstants.NODESET);
+ for (int i = 0; i < versionNodes.getLength(); i++) {
+ versions.add(versionNodes.item(i).getTextContent());
+ }
+ }
+ catch (HttpClientErrorException ex) {
+ if (ex.getStatusCode() != HttpStatus.NOT_FOUND) {
+ System.err.println("Failed to download maven-metadata.xml for " + groupId + ":" + artifactId + " from "
+ + url + ": " + ex.getMessage());
+ }
+ }
+ catch (Exception ex) {
+ System.err.println("Failed to resolve versions for module " + groupId + ":" + artifactId + " in repository "
+ + repositoryUrl + ": " + ex.getMessage());
+ }
+ return versions;
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/Upgrade.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/Upgrade.java
new file mode 100644
index 000000000000..e0457fe8f4f5
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/Upgrade.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr;
+
+import org.springframework.boot.build.bom.Library;
+import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
+
+/**
+ * An upgrade to change a {@link Library} to use a new version}.
+ *
+ * @author Andy Wilkinson
+ */
+final class Upgrade {
+
+ private final Library library;
+
+ private final DependencyVersion version;
+
+ Upgrade(Library library, DependencyVersion version) {
+ this.library = library;
+ this.version = version;
+ }
+
+ Library getLibrary() {
+ return this.library;
+ }
+
+ DependencyVersion getVersion() {
+ return this.version;
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java
new file mode 100644
index 000000000000..7da198a5f1b8
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeBom.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Properties;
+
+import javax.inject.Inject;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.internal.tasks.userinput.UserInputHandler;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.api.tasks.options.Option;
+
+import org.springframework.boot.build.bom.BomExtension;
+import org.springframework.boot.build.bom.bomr.github.GitHub;
+import org.springframework.boot.build.bom.bomr.github.GitHubRepository;
+import org.springframework.boot.build.bom.bomr.github.Milestone;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Task} to upgrade the libraries managed by a bom.
+ *
+ * @author Andy Wilkinson
+ */
+public class UpgradeBom extends DefaultTask {
+
+ private final BomExtension bom;
+
+ private String milestone;
+
+ @Inject
+ public UpgradeBom(BomExtension bom) {
+ this.bom = bom;
+ }
+
+ @Option(option = "milestone", description = "Milestone to which dependency upgrade issues should be assigned")
+ public void setMilestone(String milestone) {
+ this.milestone = milestone;
+ }
+
+ @TaskAction
+ void upgradeDependencies() {
+ GitHubRepository repository = createGitHub().getRepository(this.bom.getUpgrade().getGitHub().getOrganization(),
+ this.bom.getUpgrade().getGitHub().getRepository());
+ List availableLabels = repository.getLabels();
+ List issueLabels = this.bom.getUpgrade().getGitHub().getIssueLabels();
+ if (!availableLabels.containsAll(issueLabels)) {
+ List unknownLabels = new ArrayList<>(issueLabels);
+ unknownLabels.removeAll(availableLabels);
+ throw new InvalidUserDataException(
+ "Unknown label(s): " + StringUtils.collectionToCommaDelimitedString(unknownLabels));
+ }
+ Milestone milestone = determineMilestone(repository);
+ List upgrades = new InteractiveUpgradeResolver(
+ new MavenMetadataVersionResolver(Arrays.asList("https://repo1.maven.org/maven2/")),
+ this.bom.getUpgrade().getPolicy(), getServices().get(UserInputHandler.class))
+ .resolveUpgrades(this.bom.getLibraries());
+ for (Upgrade upgrade : upgrades) {
+ String title = "Upgrade to " + upgrade.getLibrary().getName() + " " + upgrade.getVersion();
+ System.out.println(title);
+ try {
+ Path buildFile = getProject().getBuildFile().toPath();
+ applyChanges(upgrade, buildFile);
+ int issueNumber = repository.openIssue(title, issueLabels, milestone);
+ if (new ProcessBuilder().command("git", "add", buildFile.toFile().getAbsolutePath()).start()
+ .waitFor() != 0) {
+ throw new IllegalStateException("git add failed");
+ }
+ if (new ProcessBuilder().command("git", "commit", "-m", title + "\n\nCloses gh-" + issueNumber).start()
+ .waitFor() != 0) {
+ throw new IllegalStateException("git commit failed");
+ }
+ }
+ catch (IOException ex) {
+ throw new TaskExecutionException(this, ex);
+ }
+ catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ private GitHub createGitHub() {
+ Properties bomrProperties = new Properties();
+ try (Reader reader = new FileReader(new File(System.getProperty("user.home"), ".bomr.properties"))) {
+ bomrProperties.load(reader);
+ String username = bomrProperties.getProperty("bomr.github.username");
+ String password = bomrProperties.getProperty("bomr.github.password");
+ return GitHub.withCredentials(username, password);
+ }
+ catch (IOException ex) {
+ throw new InvalidUserDataException("Failed to load .bomr.properties from user home", ex);
+ }
+ }
+
+ private void applyChanges(Upgrade upgrade, Path buildFile) throws IOException {
+ String contents = new String(Files.readAllBytes(buildFile), StandardCharsets.UTF_8);
+ String modified = contents.replace(
+ "library('" + upgrade.getLibrary().getName() + "', '" + upgrade.getLibrary().getVersion() + "')",
+ "library('" + upgrade.getLibrary().getName() + "', '" + upgrade.getVersion() + "')");
+ Files.write(buildFile, modified.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
+ }
+
+ private Milestone determineMilestone(GitHubRepository repository) {
+ if (this.milestone == null) {
+ return null;
+ }
+ List milestones = repository.getMilestones();
+ Optional matchingMilestone = milestones.stream()
+ .filter((milestone) -> milestone.getName().equals(this.milestone)).findFirst();
+ if (!matchingMilestone.isPresent()) {
+ throw new InvalidUserDataException("Unknown milestone: " + this.milestone);
+ }
+ return matchingMilestone.get();
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/ExampleController.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeResolver.java
similarity index 53%
rename from spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/ExampleController.java
rename to buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeResolver.java
index 41df3759ee6e..ed1805eca02e 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/ExampleController.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,24 +14,25 @@
* limitations under the License.
*/
-package org.springframework.boot.load.it.jar;
+package org.springframework.boot.build.bom.bomr;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.boot.build.bom.Library;
/**
- * Simple example Spring MVC Controller.
+ * Resolves upgrades for the libraries in a bom.
*
- * @author Phillip Webb
+ * @author Andy Wilkinson
*/
-@Controller
-public class ExampleController {
+interface UpgradeResolver {
- @RequestMapping("/")
- @ResponseBody
- public String helloWorld() {
- return "Hello Embedded Jar World!";
- }
+ /**
+ * Resolves the upgrades to be applied to the given {@code libraries}.
+ * @param libraries the libraries
+ * @return the upgrades
+ */
+ List resolveUpgrades(Collection libraries);
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionResolver.java
new file mode 100644
index 000000000000..70b1c9298cf8
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/VersionResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr;
+
+import java.util.SortedSet;
+
+import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
+
+/**
+ * Resolves the available versions for a module.
+ *
+ * @author Andy Wilkinson
+ */
+interface VersionResolver {
+
+ /**
+ * Resolves the available versions for the module identified by the given
+ * {@code groupId} and {@code artifactId}.
+ * @param groupId module's group ID
+ * @param artifactId module's artifact ID
+ * @return the available versions
+ */
+ SortedSet resolveVersions(String groupId, String artifactId);
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHub.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHub.java
new file mode 100644
index 000000000000..879caec26ef2
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHub.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.github;
+
+/**
+ * Minimal API for interacting with GitHub.
+ *
+ * @author Andy Wilkinson
+ */
+public interface GitHub {
+
+ /**
+ * Returns a {@link GitHubRepository} with the given {@code name} in the given
+ * {@code organization}.
+ * @param organization the organization
+ * @param name the name of the repository
+ * @return the repository
+ */
+ GitHubRepository getRepository(String organization, String name);
+
+ /**
+ * Creates a new {@code GitHub} that will authenticate with given {@code username} and
+ * {@code password}.
+ * @param username username for authentication
+ * @param password password for authentication
+ * @return the new {@code GitHub} instance
+ */
+ static GitHub withCredentials(String username, String password) {
+ return new StandardGitHub(username, password);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java
new file mode 100644
index 000000000000..3c49ba56d55f
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/GitHubRepository.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.github;
+
+import java.util.List;
+
+/**
+ * Minimal API for interacting with a GitHub repository.
+ *
+ * @author Andy Wilkinson
+ */
+public interface GitHubRepository {
+
+ /**
+ * Opens a new issue with the given title. The given {@code labels} will be applied to
+ * the issue and it will be assigned to the given {@code milestone}.
+ * @param title the title of the issue
+ * @param labels the labels to apply to the issue
+ * @param milestone the milestone to assign the issue to
+ * @return the number of the new issue
+ */
+ int openIssue(String title, List labels, Milestone milestone);
+
+ /**
+ * Returns the labels in the repository.
+ * @return the labels
+ */
+ List getLabels();
+
+ /**
+ * Returns the milestones in the repository.
+ * @return the milestones
+ */
+ List getMilestones();
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Milestone.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Milestone.java
new file mode 100644
index 000000000000..f50dd28c48dd
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/Milestone.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.github;
+
+/**
+ * A milestone in a {@link GitHubRepository GitHub repository}.
+ *
+ * @author Andy Wilkinson
+ */
+public class Milestone {
+
+ private final String name;
+
+ private final int number;
+
+ Milestone(String name, int number) {
+ this.name = name;
+ this.number = number;
+ }
+
+ /**
+ * Returns the name of the milestone.
+ * @return the name
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns the number of the milestone.
+ * @return the number
+ */
+ public int getNumber() {
+ return this.number;
+ }
+
+ @Override
+ public String toString() {
+ return this.name + " (" + this.number + ")";
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHub.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHub.java
new file mode 100644
index 000000000000..5be432a0ac92
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHub.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.github;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Base64;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.DefaultUriBuilderFactory;
+import org.springframework.web.util.UriTemplateHandler;
+
+/**
+ * Standard implementation of {@link GitHub}.
+ *
+ * @author Andy Wilkinson
+ */
+final class StandardGitHub implements GitHub {
+
+ private final String username;
+
+ private final String password;
+
+ StandardGitHub(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public GitHubRepository getRepository(String organization, String name) {
+ RestTemplate restTemplate = new RestTemplate(
+ Arrays.asList(new MappingJackson2HttpMessageConverter(new ObjectMapper())));
+ restTemplate.getInterceptors().add(new ClientHttpRequestInterceptor() {
+
+ @Override
+ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
+ throws IOException {
+ request.getHeaders().add("User-Agent", StandardGitHub.this.username);
+ request.getHeaders().add("Authorization", "Basic " + Base64.getEncoder().encodeToString(
+ (StandardGitHub.this.username + ":" + StandardGitHub.this.password).getBytes()));
+ request.getHeaders().add("Accept", MediaType.APPLICATION_JSON_VALUE);
+ return execution.execute(request, body);
+ }
+
+ });
+ UriTemplateHandler uriTemplateHandler = new DefaultUriBuilderFactory(
+ "https://api.github.com/repos/" + organization + "/" + name + "/");
+ restTemplate.setUriTemplateHandler(uriTemplateHandler);
+ return new StandardGitHubRepository(restTemplate);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java
new file mode 100644
index 000000000000..6eced352719a
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHubRepository.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.github;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Standard implementation of {@link GitHubRepository}.
+ *
+ * @author Andy Wilkinson
+ */
+final class StandardGitHubRepository implements GitHubRepository {
+
+ private final RestTemplate rest;
+
+ StandardGitHubRepository(RestTemplate restTemplate) {
+ this.rest = restTemplate;
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public int openIssue(String title, List labels, Milestone milestone) {
+ Map body = new HashMap<>();
+ body.put("title", title);
+ if (milestone != null) {
+ body.put("milestone", milestone.getNumber());
+ }
+ if (!labels.isEmpty()) {
+ body.put("labels", labels);
+ }
+ ResponseEntity response = this.rest.postForEntity("issues", body, Map.class);
+ return (Integer) response.getBody().get("number");
+ }
+
+ @Override
+ public List getLabels() {
+ return get("labels?per_page=100", (label) -> (String) label.get("name"));
+ }
+
+ @Override
+ public List getMilestones() {
+ return get("milestones?per_page=100",
+ (milestone) -> new Milestone((String) milestone.get("title"), (Integer) milestone.get("number")));
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private List get(String name, Function, T> mapper) {
+ ResponseEntity response = this.rest.getForEntity(name, List.class);
+ List> body = response.getBody();
+ return body.stream().map(mapper).collect(Collectors.toList());
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/AbstractDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/AbstractDependencyVersion.java
new file mode 100644
index 000000000000..0192c64f6f5c
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/AbstractDependencyVersion.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import org.apache.maven.artifact.versioning.ComparableVersion;
+
+/**
+ * Base class for {@link DependencyVersion} implementations.
+ *
+ * @author Andy Wilkinson
+ */
+abstract class AbstractDependencyVersion implements DependencyVersion {
+
+ private final ComparableVersion comparableVersion;
+
+ protected AbstractDependencyVersion(ComparableVersion comparableVersion) {
+ this.comparableVersion = comparableVersion;
+ }
+
+ @Override
+ public int compareTo(DependencyVersion other) {
+ ComparableVersion otherComparable = (other instanceof AbstractDependencyVersion)
+ ? ((AbstractDependencyVersion) other).comparableVersion : new ComparableVersion(other.toString());
+ return this.comparableVersion.compareTo(otherComparable);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AbstractDependencyVersion other = (AbstractDependencyVersion) obj;
+ if (!this.comparableVersion.equals(other.comparableVersion)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.comparableVersion.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.comparableVersion.toString();
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersion.java
new file mode 100644
index 000000000000..8ff9408ef9b2
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersion.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import java.util.Optional;
+
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.ComparableVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+
+/**
+ * A {@link DependencyVersion} backed by an {@link ArtifactVersion}.
+ *
+ * @author Andy Wilkinson
+ */
+class ArtifactVersionDependencyVersion extends AbstractDependencyVersion {
+
+ private final ArtifactVersion artifactVersion;
+
+ protected ArtifactVersionDependencyVersion(ArtifactVersion artifactVersion) {
+ super(new ComparableVersion(artifactVersion.toString()));
+ this.artifactVersion = artifactVersion;
+ }
+
+ protected ArtifactVersionDependencyVersion(ArtifactVersion artifactVersion, ComparableVersion comparableVersion) {
+ super(comparableVersion);
+ this.artifactVersion = artifactVersion;
+ }
+
+ @Override
+ public boolean isNewerThan(DependencyVersion other) {
+ if (other instanceof ReleaseTrainDependencyVersion) {
+ return false;
+ }
+ return compareTo(other) > 0;
+ }
+
+ @Override
+ public boolean isSameMajorAndNewerThan(DependencyVersion other) {
+ if (other instanceof ReleaseTrainDependencyVersion) {
+ return false;
+ }
+ return extractArtifactVersionDependencyVersion(other).map(this::isSameMajorAndNewerThan).orElse(true);
+ }
+
+ private boolean isSameMajorAndNewerThan(ArtifactVersionDependencyVersion other) {
+ return this.artifactVersion.getMajorVersion() == other.artifactVersion.getMajorVersion() && isNewerThan(other);
+ }
+
+ @Override
+ public boolean isSameMinorAndNewerThan(DependencyVersion other) {
+ if (other instanceof ReleaseTrainDependencyVersion) {
+ return false;
+ }
+ return extractArtifactVersionDependencyVersion(other).map(this::isSameMinorAndNewerThan).orElse(true);
+ }
+
+ private boolean isSameMinorAndNewerThan(ArtifactVersionDependencyVersion other) {
+ return this.artifactVersion.getMajorVersion() == other.artifactVersion.getMajorVersion()
+ && this.artifactVersion.getMinorVersion() == other.artifactVersion.getMinorVersion()
+ && isNewerThan(other);
+ }
+
+ @Override
+ public String toString() {
+ return this.artifactVersion.toString();
+ }
+
+ private Optional extractArtifactVersionDependencyVersion(
+ DependencyVersion other) {
+ ArtifactVersionDependencyVersion artifactVersion = null;
+ if (other instanceof ArtifactVersionDependencyVersion) {
+ artifactVersion = (ArtifactVersionDependencyVersion) other;
+ }
+ return Optional.ofNullable(artifactVersion);
+ }
+
+ static ArtifactVersionDependencyVersion parse(String version) {
+ ArtifactVersion artifactVersion = new DefaultArtifactVersion(version);
+ if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(version)) {
+ return null;
+ }
+ return new ArtifactVersionDependencyVersion(artifactVersion);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/CombinedPatchAndQualifierDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/CombinedPatchAndQualifierDependencyVersion.java
new file mode 100644
index 000000000000..8a910d6b60b7
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/CombinedPatchAndQualifierDependencyVersion.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+
+/**
+ * A {@link DependencyVersion} where the patch and qualifier are not separated.
+ *
+ * @author Andy Wilkinson
+ */
+final class CombinedPatchAndQualifierDependencyVersion extends ArtifactVersionDependencyVersion {
+
+ private static final Pattern PATTERN = Pattern.compile("([0-9]+\\.[0-9]+\\.[0-9]+)([A-Za-z][A-Za-z0-9]+)");
+
+ private final String original;
+
+ private CombinedPatchAndQualifierDependencyVersion(ArtifactVersion artifactVersion, String original) {
+ super(artifactVersion);
+ this.original = original;
+ }
+
+ @Override
+ public String toString() {
+ return this.original;
+ }
+
+ static CombinedPatchAndQualifierDependencyVersion parse(String version) {
+ Matcher matcher = PATTERN.matcher(version);
+ if (!matcher.matches()) {
+ return null;
+ }
+ ArtifactVersion artifactVersion = new DefaultArtifactVersion(matcher.group(1) + "." + matcher.group(2));
+ if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(version)) {
+ return null;
+ }
+ return new CombinedPatchAndQualifierDependencyVersion(artifactVersion, version);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/DependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/DependencyVersion.java
new file mode 100644
index 000000000000..a344a5c0e68e
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/DependencyVersion.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Version of a dependency.
+ *
+ * @author Andy Wilkinson
+ */
+public interface DependencyVersion extends Comparable {
+
+ /**
+ * Returns whether this version is newer than the given {@code other} version.
+ * @param other version to test
+ * @return {@code true} if this version is newer, otherwise {@code false}
+ */
+ boolean isNewerThan(DependencyVersion other);
+
+ /**
+ * Returns whether this version has the same major versions as the {@code other}
+ * version while also being newer.
+ * @param other version to test
+ * @return {@code true} if this version has the same major and is newer, otherwise
+ * {@code false}
+ */
+ boolean isSameMajorAndNewerThan(DependencyVersion other);
+
+ /**
+ * Returns whether this version has the same major and minor versions as the
+ * {@code other} version while also being newer.
+ * @param other version to test
+ * @return {@code true} if this version has the same major and minor and is newer,
+ * otherwise {@code false}
+ */
+ boolean isSameMinorAndNewerThan(DependencyVersion other);
+
+ static DependencyVersion parse(String version) {
+ List> parsers = Arrays.asList(ArtifactVersionDependencyVersion::parse,
+ ReleaseTrainDependencyVersion::parse, NumericQualifierDependencyVersion::parse,
+ CombinedPatchAndQualifierDependencyVersion::parse, LeadingZeroesDependencyVersion::parse,
+ UnstructuredDependencyVersion::parse);
+ for (Function parser : parsers) {
+ DependencyVersion result = parser.apply(version);
+ if (result != null) {
+ return result;
+ }
+ }
+ throw new IllegalArgumentException("Version '" + version + "' could not be parsed");
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/LeadingZeroesDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/LeadingZeroesDependencyVersion.java
new file mode 100644
index 000000000000..5514b8b652a7
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/LeadingZeroesDependencyVersion.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+
+/**
+ * A {@link DependencyVersion} that tolerates leading zeroes.
+ *
+ * @author Andy Wilkinson
+ */
+final class LeadingZeroesDependencyVersion extends ArtifactVersionDependencyVersion {
+
+ private static final Pattern PATTERN = Pattern.compile("0*([0-9]+)\\.0*([0-9]+)\\.0*([0-9]+)");
+
+ private final String original;
+
+ private LeadingZeroesDependencyVersion(ArtifactVersion artifactVersion, String original) {
+ super(artifactVersion);
+ this.original = original;
+ }
+
+ @Override
+ public String toString() {
+ return this.original;
+ }
+
+ static LeadingZeroesDependencyVersion parse(String input) {
+ Matcher matcher = PATTERN.matcher(input);
+ if (!matcher.matches()) {
+ return null;
+ }
+ ArtifactVersion artifactVersion = new DefaultArtifactVersion(
+ matcher.group(1) + matcher.group(2) + matcher.group(3));
+ return new LeadingZeroesDependencyVersion(artifactVersion, input);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/NumericQualifierDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/NumericQualifierDependencyVersion.java
new file mode 100644
index 000000000000..363def7d8010
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/NumericQualifierDependencyVersion.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.ComparableVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+
+/**
+ * A fallback {@link DependencyVersion} to handle versions with four components that
+ * cannot be handled by {@link ArtifactVersion} because the fourth component is numeric.
+ *
+ * @author Andy Wilkinson
+ */
+final class NumericQualifierDependencyVersion extends ArtifactVersionDependencyVersion {
+
+ private final String original;
+
+ private NumericQualifierDependencyVersion(ArtifactVersion artifactVersion, String original) {
+ super(artifactVersion, new ComparableVersion(original));
+ this.original = original;
+ }
+
+ @Override
+ public String toString() {
+ return this.original;
+ }
+
+ static NumericQualifierDependencyVersion parse(String input) {
+ String[] components = input.split("\\.");
+ if (components.length == 4) {
+ ArtifactVersion artifactVersion = new DefaultArtifactVersion(
+ components[0] + "." + components[1] + "." + components[2]);
+ if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(input)) {
+ return null;
+ }
+ return new NumericQualifierDependencyVersion(artifactVersion, input);
+ }
+ return null;
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersion.java
new file mode 100644
index 000000000000..b3626d19846d
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersion.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link DependencyVersion} for a release train such as Spring Data.
+ *
+ * @author Andy Wilkinson
+ */
+final class ReleaseTrainDependencyVersion implements DependencyVersion {
+
+ private static final Pattern VERSION_PATTERN = Pattern.compile("([A-Z][a-z]+)-([A-Z]+)([0-9]*)");
+
+ private final String releaseTrain;
+
+ private final String type;
+
+ private final int version;
+
+ private final String original;
+
+ private ReleaseTrainDependencyVersion(String releaseTrain, String type, int version, String original) {
+ this.releaseTrain = releaseTrain;
+ this.type = type;
+ this.version = version;
+ this.original = original;
+ }
+
+ @Override
+ public int compareTo(DependencyVersion other) {
+ if (!(other instanceof ReleaseTrainDependencyVersion)) {
+ return 0;
+ }
+ ReleaseTrainDependencyVersion otherReleaseTrain = (ReleaseTrainDependencyVersion) other;
+ int comparison = this.releaseTrain.compareTo(otherReleaseTrain.releaseTrain);
+ if (comparison != 0) {
+ return comparison;
+ }
+ comparison = this.type.compareTo(otherReleaseTrain.type);
+ if (comparison != 0) {
+ return comparison;
+ }
+ return Integer.compare(this.version, otherReleaseTrain.version);
+ }
+
+ @Override
+ public boolean isNewerThan(DependencyVersion other) {
+ if (!(other instanceof ReleaseTrainDependencyVersion)) {
+ return true;
+ }
+ ReleaseTrainDependencyVersion otherReleaseTrain = (ReleaseTrainDependencyVersion) other;
+ return otherReleaseTrain.compareTo(this) < 0;
+ }
+
+ @Override
+ public boolean isSameMajorAndNewerThan(DependencyVersion other) {
+ return isNewerThan(other);
+ }
+
+ @Override
+ public boolean isSameMinorAndNewerThan(DependencyVersion other) {
+ if (!(other instanceof ReleaseTrainDependencyVersion)) {
+ return true;
+ }
+ ReleaseTrainDependencyVersion otherReleaseTrain = (ReleaseTrainDependencyVersion) other;
+ return otherReleaseTrain.releaseTrain.equals(this.releaseTrain) && isNewerThan(other);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ReleaseTrainDependencyVersion other = (ReleaseTrainDependencyVersion) obj;
+ if (!this.original.equals(other.original)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.original.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.original;
+ }
+
+ static ReleaseTrainDependencyVersion parse(String input) {
+ Matcher matcher = VERSION_PATTERN.matcher(input);
+ if (!matcher.matches()) {
+ return null;
+ }
+ return new ReleaseTrainDependencyVersion(matcher.group(1), matcher.group(2),
+ (StringUtils.hasLength(matcher.group(3))) ? Integer.parseInt(matcher.group(3)) : 0, input);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/UnstructuredDependencyVersion.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/UnstructuredDependencyVersion.java
new file mode 100644
index 000000000000..02822beaec99
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/version/UnstructuredDependencyVersion.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import org.apache.maven.artifact.versioning.ComparableVersion;
+
+/**
+ * A {@link DependencyVersion} with no structure such that version comparisons are not
+ * possible.
+ *
+ * @author Andy Wilkinson
+ */
+final class UnstructuredDependencyVersion extends AbstractDependencyVersion implements DependencyVersion {
+
+ private final String version;
+
+ private UnstructuredDependencyVersion(String version) {
+ super(new ComparableVersion(version));
+ this.version = version;
+ }
+
+ @Override
+ public boolean isNewerThan(DependencyVersion other) {
+ return this.compareTo(other) > 0;
+ }
+
+ @Override
+ public boolean isSameMajorAndNewerThan(DependencyVersion other) {
+ return this.compareTo(other) > 0;
+ }
+
+ @Override
+ public boolean isSameMinorAndNewerThan(DependencyVersion other) {
+ return this.compareTo(other) > 0;
+ }
+
+ @Override
+ public String toString() {
+ return this.version;
+ }
+
+ static UnstructuredDependencyVersion parse(String version) {
+ return new UnstructuredDependencyVersion(version);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForConflicts.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForConflicts.java
new file mode 100644
index 000000000000..b5096ed11593
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForConflicts.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2012-2020 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.classpath;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Predicate;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.Classpath;
+import org.gradle.api.tasks.TaskAction;
+
+/**
+ * A {@link Task} for checking the classpath for conflicting classes and resources.
+ *
+ * @author Andy Wilkinson
+ */
+public class CheckClasspathForConflicts extends DefaultTask {
+
+ private final List> ignores = new ArrayList<>();
+
+ private FileCollection classpath;
+
+ public void setClasspath(FileCollection classpath) {
+ this.classpath = classpath;
+ }
+
+ @Classpath
+ public FileCollection getClasspath() {
+ return this.classpath;
+ }
+
+ @TaskAction
+ public void checkForConflicts() throws IOException {
+ ClasspathContents classpathContents = new ClasspathContents();
+ for (File file : this.classpath) {
+ if (file.isDirectory()) {
+ Path root = file.toPath();
+ Files.walk(root).filter((path) -> Files.isRegularFile(path))
+ .forEach((entry) -> classpathContents.add(root.relativize(entry).toString(), root.toString()));
+ }
+ else {
+ try (JarFile jar = new JarFile(file)) {
+ for (JarEntry entry : Collections.list(jar.entries())) {
+ if (!entry.isDirectory()) {
+ classpathContents.add(entry.getName(), file.getAbsolutePath());
+ }
+ }
+ }
+ }
+ }
+ Map> conflicts = classpathContents.getConflicts(this.ignores);
+ if (!conflicts.isEmpty()) {
+ StringBuilder message = new StringBuilder(String.format("Found classpath conflicts:%n"));
+ conflicts.forEach((entry, locations) -> {
+ message.append(String.format(" %s%n", entry));
+ locations.forEach((location) -> message.append(String.format(" %s%n", location)));
+ });
+ throw new GradleException(message.toString());
+ }
+ }
+
+ public void ignore(Predicate predicate) {
+ this.ignores.add(predicate);
+ }
+
+ private static final class ClasspathContents {
+
+ private static final Set IGNORED_NAMES = new HashSet<>(Arrays.asList("about.html", "changelog.txt",
+ "LICENSE", "license.txt", "module-info.class", "notice.txt", "readme.txt"));
+
+ private final Map> classpathContents = new HashMap<>();
+
+ private void add(String name, String source) {
+ this.classpathContents.computeIfAbsent(name, (key) -> new ArrayList<>()).add(source);
+ }
+
+ private Map> getConflicts(List> ignores) {
+ return this.classpathContents.entrySet().stream().filter((entry) -> entry.getValue().size() > 1)
+ .filter((entry) -> canConflict(entry.getKey(), ignores))
+ .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v1, TreeMap::new));
+ }
+
+ private boolean canConflict(String name, List> ignores) {
+ if (name.startsWith("META-INF/")) {
+ return false;
+ }
+ for (String ignoredName : IGNORED_NAMES) {
+ if (name.equals(ignoredName)) {
+ return false;
+ }
+ }
+ for (Predicate ignore : ignores) {
+ if (ignore.test(name)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForProhibitedDependencies.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForProhibitedDependencies.java
new file mode 100644
index 000000000000..301304deb712
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForProhibitedDependencies.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012-2020 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.classpath;
+
+import java.io.IOException;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.Classpath;
+import org.gradle.api.tasks.TaskAction;
+
+/**
+ * A {@link Task} for checking the classpath for prohibited dependencies.
+ *
+ * @author Andy Wilkinson
+ */
+public class CheckClasspathForProhibitedDependencies extends DefaultTask {
+
+ private Configuration classpath;
+
+ public CheckClasspathForProhibitedDependencies() {
+ getOutputs().upToDateWhen((task) -> true);
+ }
+
+ public void setClasspath(Configuration classpath) {
+ this.classpath = classpath;
+ }
+
+ @Classpath
+ public FileCollection getClasspath() {
+ return this.classpath;
+ }
+
+ @TaskAction
+ public void checkForProhibitedDependencies() throws IOException {
+ TreeSet prohibited = this.classpath.getResolvedConfiguration().getResolvedArtifacts().stream()
+ .map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited)
+ .map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new));
+ if (!prohibited.isEmpty()) {
+ StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies:%n"));
+ for (String dependency : prohibited) {
+ message.append(String.format(" %s%n", dependency));
+ }
+ throw new GradleException(message.toString());
+ }
+ }
+
+ private boolean prohibited(ModuleVersionIdentifier id) {
+ String group = id.getGroup();
+ if (group.equals("javax.batch")) {
+ return false;
+ }
+ if (group.startsWith("javax")) {
+ return true;
+ }
+ if (group.equals("commons-logging")) {
+ return true;
+ }
+ if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/cli/AbstractPackageManagerDefinitionTask.java b/buildSrc/src/main/java/org/springframework/boot/build/cli/AbstractPackageManagerDefinitionTask.java
new file mode 100644
index 000000000000..f0592979d396
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/cli/AbstractPackageManagerDefinitionTask.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2019 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.cli;
+
+import java.io.File;
+import java.security.MessageDigest;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Project;
+import org.gradle.api.file.RegularFile;
+import org.gradle.api.provider.Provider;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskExecutionException;
+
+/**
+ * Base class for generating a package manager definition file such as a Scoop manifest or
+ * a Homebrew formula.
+ *
+ * @author Andy Wilkinson
+ */
+public abstract class AbstractPackageManagerDefinitionTask extends DefaultTask {
+
+ private Provider archive;
+
+ private File template;
+
+ private File outputDir;
+
+ public AbstractPackageManagerDefinitionTask() {
+ getInputs().property("version", getProject().provider(getProject()::getVersion));
+ }
+
+ @InputFile
+ public RegularFile getArchive() {
+ return this.archive.get();
+ }
+
+ public void setArchive(Provider archive) {
+ this.archive = archive;
+ }
+
+ @InputFile
+ public File getTemplate() {
+ return this.template;
+ }
+
+ public void setTemplate(File template) {
+ this.template = template;
+ }
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return this.outputDir;
+ }
+
+ public void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ protected void createDescriptor(Map additionalProperties) {
+ getProject().copy((copy) -> {
+ copy.from(this.template);
+ copy.into(this.outputDir);
+ Map properties = new HashMap<>(additionalProperties);
+ properties.put("hash", sha256(this.archive.get().getAsFile()));
+ properties.put("repo", determineArtifactoryRepo(getProject()));
+ properties.put("project", getProject());
+ copy.expand(properties);
+ });
+ }
+
+ private String sha256(File file) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ return new DigestUtils(digest).digestAsHex(file);
+ }
+ catch (Exception ex) {
+ throw new TaskExecutionException(this, ex);
+ }
+ }
+
+ private String determineArtifactoryRepo(Project project) {
+ String version = project.getVersion().toString();
+ String type = version.substring(version.lastIndexOf('.'));
+ if (type.equals("RELEASE")) {
+ return "release";
+ }
+ if (type.startsWith("M") || type.startsWith("RC")) {
+ return "milestone";
+ }
+ return "snapshot";
+ }
+
+}
diff --git a/spring-boot-tests/spring-boot-deployment-tests/spring-boot-deployment-test-wildfly/src/main/java/sample/SampleController.java b/buildSrc/src/main/java/org/springframework/boot/build/cli/HomebrewFormula.java
similarity index 57%
rename from spring-boot-tests/spring-boot-deployment-tests/spring-boot-deployment-test-wildfly/src/main/java/sample/SampleController.java
rename to buildSrc/src/main/java/org/springframework/boot/build/cli/HomebrewFormula.java
index 819f68150b4b..5592d3061756 100644
--- a/spring-boot-tests/spring-boot-deployment-tests/spring-boot-deployment-test-wildfly/src/main/java/sample/SampleController.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/cli/HomebrewFormula.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,17 +14,22 @@
* limitations under the License.
*/
-package sample;
+package org.springframework.boot.build.cli;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RestController;
+import java.util.Collections;
-@RestController
-public class SampleController {
+import org.gradle.api.tasks.TaskAction;
- @GetMapping("/")
- public String hello() {
- return "Hello World";
+/**
+ * A {@Task} for creating a Homebrew formula manifest.
+ *
+ * @author Andy Wilkinson
+ */
+public class HomebrewFormula extends AbstractPackageManagerDefinitionTask {
+
+ @TaskAction
+ void createFormula() {
+ createDescriptor(Collections.emptyMap());
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/ExampleController.java b/buildSrc/src/main/java/org/springframework/boot/build/cli/ScoopManifest.java
similarity index 53%
rename from spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/ExampleController.java
rename to buildSrc/src/main/java/org/springframework/boot/build/cli/ScoopManifest.java
index 41df3759ee6e..de671c8da14c 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/ExampleController.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/cli/ScoopManifest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,24 +14,23 @@
* limitations under the License.
*/
-package org.springframework.boot.load.it.jar;
+package org.springframework.boot.build.cli;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
+import java.util.Collections;
+
+import org.gradle.api.tasks.TaskAction;
/**
- * Simple example Spring MVC Controller.
+ * A {@Task} for creating a Scoop manifest.
*
- * @author Phillip Webb
+ * @author Andy Wilkinson
*/
-@Controller
-public class ExampleController {
+public class ScoopManifest extends AbstractPackageManagerDefinitionTask {
- @RequestMapping("/")
- @ResponseBody
- public String helloWorld() {
- return "Hello Embedded Jar World!";
+ @TaskAction
+ void createManifest() {
+ String version = getProject().getVersion().toString();
+ createDescriptor(Collections.singletonMap("scoopVersion", version.substring(0, version.lastIndexOf('.'))));
}
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentConstrainedVersions.java b/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentConstrainedVersions.java
new file mode 100644
index 000000000000..101039e9fd8f
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/constraints/DocumentConstrainedVersions.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2019 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.constraints;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.model.ObjectFactory;
+import org.gradle.api.provider.SetProperty;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import org.springframework.boot.build.constraints.ExtractVersionConstraints.ConstrainedVersion;
+
+/**
+ * Task for documenting a platform's constrained versions.
+ *
+ * @author Andy Wilkinson
+ */
+public class DocumentConstrainedVersions extends DefaultTask {
+
+ private final SetProperty constrainedVersions;
+
+ private File outputFile;
+
+ @Inject
+ public DocumentConstrainedVersions(ObjectFactory objectFactory) {
+ this.constrainedVersions = objectFactory.setProperty(ConstrainedVersion.class);
+ }
+
+ @Input
+ public SetProperty getConstrainedVersions() {
+ return this.constrainedVersions;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return this.outputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ @TaskAction
+ public void documentConstrainedVersions() throws IOException {
+ this.outputFile.getParentFile().mkdirs();
+ try (PrintWriter writer = new PrintWriter(new FileWriter(this.outputFile))) {
+ writer.println("|===");
+ writer.println("| Group ID | Artifact ID | Version");
+ for (ConstrainedVersion constrainedVersion : this.constrainedVersions.get()) {
+ writer.println();
+ writer.printf("| `%s`%n", constrainedVersion.getGroup());
+ writer.printf("| `%s`%n", constrainedVersion.getArtifact());
+ writer.printf("| `%s`%n", constrainedVersion.getVersion());
+ }
+ 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
new file mode 100644
index 000000000000..3151560b7f6e
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/constraints/ExtractVersionConstraints.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2019 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.constraints;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.ComponentMetadataDetails;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.DependencyConstraint;
+import org.gradle.api.artifacts.DependencyConstraintMetadata;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.Internal;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.platform.base.Platform;
+
+/**
+ * {@link Task} to extract constraints from a {@link Platform}. The platform's own
+ * constraints and those in any boms upon which it depends are extracted.
+ *
+ * @author Andy Wilkinson
+ */
+public class ExtractVersionConstraints extends AbstractTask {
+
+ private final Configuration configuration;
+
+ private final Map versionConstraints = new TreeMap<>();
+
+ private final Set constrainedVersions = new TreeSet<>();
+
+ private final List projectPaths = new ArrayList();
+
+ public ExtractVersionConstraints() {
+ DependencyHandler dependencies = getProject().getDependencies();
+ this.configuration = getProject().getConfigurations().create(getName());
+ dependencies.getComponents().all(this::processMetadataDetails);
+ }
+
+ public void enforcedPlatform(String projectPath) {
+ this.configuration.getDependencies().add(getProject().getDependencies().enforcedPlatform(
+ getProject().getDependencies().project(Collections.singletonMap("path", projectPath))));
+ this.projectPaths.add(projectPath);
+ }
+
+ @Internal
+ public Map getVersionConstraints() {
+ return Collections.unmodifiableMap(this.versionConstraints);
+ }
+
+ @Internal
+ public Set getConstrainedVersions() {
+ return this.constrainedVersions;
+ }
+
+ @TaskAction
+ void extractVersionConstraints() {
+ this.configuration.resolve();
+ for (String projectPath : this.projectPaths) {
+ for (DependencyConstraint constraint : getProject().project(projectPath).getConfigurations()
+ .getByName("apiElements").getAllDependencyConstraints()) {
+ this.versionConstraints.put(constraint.getGroup() + ":" + constraint.getName(),
+ constraint.getVersionConstraint().toString());
+ this.constrainedVersions.add(new ConstrainedVersion(constraint.getGroup(), constraint.getName(),
+ constraint.getVersionConstraint().toString()));
+ }
+ }
+ }
+
+ private void processMetadataDetails(ComponentMetadataDetails details) {
+ details.allVariants((variantMetadata) -> variantMetadata.withDependencyConstraints((dependencyConstraints) -> {
+ for (DependencyConstraintMetadata constraint : dependencyConstraints) {
+ this.versionConstraints.put(constraint.getGroup() + ":" + constraint.getName(),
+ constraint.getVersionConstraint().toString());
+ this.constrainedVersions.add(new ConstrainedVersion(constraint.getGroup(), constraint.getName(),
+ constraint.getVersionConstraint().toString()));
+ }
+ }));
+ }
+
+ public static final class ConstrainedVersion implements Comparable, Serializable {
+
+ private final String group;
+
+ private final String artifact;
+
+ private final String version;
+
+ private ConstrainedVersion(String group, String artifact, String version) {
+ this.group = group;
+ this.artifact = artifact;
+ this.version = version;
+ }
+
+ public String getGroup() {
+ return this.group;
+ }
+
+ public String getArtifact() {
+ return this.artifact;
+ }
+
+ public String getVersion() {
+ return this.version;
+ }
+
+ @Override
+ public int compareTo(ConstrainedVersion other) {
+ int groupComparison = this.group.compareTo(other.group);
+ if (groupComparison != 0) {
+ return groupComparison;
+ }
+ return this.artifact.compareTo(other.artifact);
+ }
+
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/AsciidocBuilder.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/AsciidocBuilder.java
similarity index 91%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/AsciidocBuilder.java
rename to buildSrc/src/main/java/org/springframework/boot/build/context/properties/AsciidocBuilder.java
index e4cab45468db..305da3abcfe5 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/AsciidocBuilder.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/AsciidocBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
/**
* Simple builder to help construct Asciidoc markup.
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/CompoundConfigurationTableEntry.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundConfigurationTableEntry.java
similarity index 77%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/CompoundConfigurationTableEntry.java
rename to buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundConfigurationTableEntry.java
index 603e902f5dce..f045b5d73089 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/CompoundConfigurationTableEntry.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundConfigurationTableEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;
-import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
-
/**
* Table entry regrouping a list of configuration properties sharing the same description.
*
@@ -39,8 +37,8 @@ class CompoundConfigurationTableEntry extends ConfigurationTableEntry {
this.configurationKeys = new TreeSet<>();
}
- void addConfigurationKeys(ConfigurationMetadataProperty... properties) {
- Stream.of(properties).map(ConfigurationMetadataProperty::getId).forEach(this.configurationKeys::add);
+ void addConfigurationKeys(ConfigurationProperty... properties) {
+ Stream.of(properties).map(ConfigurationProperty::getName).forEach(this.configurationKeys::add);
}
@Override
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationMetadataDocumentWriter.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationMetadataDocumentWriter.java
similarity index 77%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationMetadataDocumentWriter.java
rename to buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationMetadataDocumentWriter.java
index 0269c91cf4db..f56610732219 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationMetadataDocumentWriter.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationMetadataDocumentWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
import java.io.IOException;
-import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -28,8 +27,7 @@
import java.util.Map;
import java.util.stream.Collectors;
-import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
-import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder;
+import org.gradle.api.file.FileCollection;
/**
* Write Asciidoc documents with configuration properties listings.
@@ -39,14 +37,13 @@
*/
public class ConfigurationMetadataDocumentWriter {
- public void writeDocument(Path outputDirectory, DocumentOptions options, InputStream... metadata)
+ public void writeDocument(Path outputDirectory, DocumentOptions options, FileCollection metadataFiles)
throws IOException {
assertValidOutputDirectory(outputDirectory);
if (!Files.exists(outputDirectory)) {
Files.createDirectory(outputDirectory);
}
- assertMetadata(metadata);
- List tables = createConfigTables(getMetadataProperties(metadata), options);
+ List tables = createConfigTables(ConfigurationProperties.fromFiles(metadataFiles), options);
for (ConfigurationTable table : tables) {
writeConfigurationTable(table, outputDirectory);
}
@@ -61,24 +58,11 @@ private void assertValidOutputDirectory(Path outputDirPath) {
}
}
- private void assertMetadata(InputStream... metadata) {
- if (metadata == null || metadata.length < 1) {
- throw new IllegalArgumentException("missing input metadata");
- }
- }
-
- private Map getMetadataProperties(InputStream... metadata)
- throws IOException {
- ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder
- .create(metadata);
- return builder.build().getAllProperties();
- }
-
- private List createConfigTables(Map metadataProperties,
+ private List createConfigTables(Map metadataProperties,
DocumentOptions options) {
List tables = new ArrayList<>();
List unmappedKeys = metadataProperties.values().stream().filter((property) -> !property.isDeprecated())
- .map(ConfigurationMetadataProperty::getId).collect(Collectors.toList());
+ .map(ConfigurationProperty::getName).collect(Collectors.toList());
Map overrides = getOverrides(metadataProperties, unmappedKeys,
options);
options.getMetadataSections().forEach((id, keyPrefixes) -> tables
@@ -95,8 +79,7 @@ private List createConfigTables(Map getOverrides(
- Map metadataProperties, List unmappedKeys,
- DocumentOptions options) {
+ Map metadataProperties, List unmappedKeys, DocumentOptions options) {
Map overrides = new HashMap<>();
options.getOverrides().forEach((keyPrefix, description) -> {
CompoundConfigurationTableEntry entry = new CompoundConfigurationTableEntry(keyPrefix, description);
@@ -111,7 +94,7 @@ private Map getOverrides(
return overrides;
}
- private ConfigurationTable createConfigTable(Map metadataProperties,
+ private ConfigurationTable createConfigTable(Map metadataProperties,
List unmappedKeys, Map overrides, String id,
List keyPrefixes) {
ConfigurationTable table = new ConfigurationTable(id);
@@ -123,7 +106,7 @@ private ConfigurationTable createConfigTable(Map matchingKeys = unmappedKeys.stream()
.filter((key) -> keyPrefixes.stream().anyMatch(key::startsWith)).collect(Collectors.toList());
for (String matchingKey : matchingKeys) {
- ConfigurationMetadataProperty property = metadataProperties.get(matchingKey);
+ ConfigurationProperty property = metadataProperties.get(matchingKey);
table.addEntry(new SingleConfigurationTableEntry(property));
}
unmappedKeys.removeAll(matchingKeys);
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperties.java
new file mode 100644
index 000000000000..5fa43adee13c
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperties.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012-2020 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.context.properties;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import org.gradle.api.file.FileCollection;
+
+/**
+ * Configuration properties read from one or more
+ * {@code META-INF/spring-configuration-metadata.json} files.
+ *
+ * @author Andy Wilkinson
+ */
+final class ConfigurationProperties {
+
+ private static final Type MAP_TYPE = new MapTypeToken().getType();
+
+ private ConfigurationProperties() {
+
+ }
+
+ @SuppressWarnings("unchecked")
+ static Map fromFiles(FileCollection files) {
+ List configurationProperties = new ArrayList<>();
+ try {
+ Gson gson = new GsonBuilder().create();
+ for (File file : files) {
+ try (Reader reader = new FileReader(file)) {
+ Map json = gson.fromJson(reader, MAP_TYPE);
+ List> properties = (List>) json.get("properties");
+ for (Map property : properties) {
+ String name = (String) property.get("name");
+ String type = (String) property.get("type");
+ Object defaultValue = property.get("defaultValue");
+ String description = (String) property.get("description");
+ boolean deprecated = property.containsKey("deprecated");
+ configurationProperties
+ .add(new ConfigurationProperty(name, type, defaultValue, description, deprecated));
+ }
+ }
+ }
+ return configurationProperties.stream()
+ .collect(Collectors.toMap(ConfigurationProperty::getName, Function.identity()));
+ }
+ catch (IOException ex) {
+ throw new RuntimeException("Failed to load configuration metadata", ex);
+ }
+ }
+
+ private static final class MapTypeToken extends TypeToken> {
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesPlugin.java
new file mode 100644
index 000000000000..81f51281fb46
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesPlugin.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2012-2020 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.context.properties;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.compile.JavaCompile;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Plugin} for projects that define {@code @ConfigurationProperties}. When applied,
+ * the plugin reacts to the presence of the {@link JavaPlugin} by:
+ *
+ *
+ * Adding a dependency on the configuration properties annotation processor.
+ * Configure the additional metadata locations annotation processor compiler argument
+ * Defining an artifact for the resulting configuration property metadata so that it
+ * can be consumed by downstream projects.
+ *
+ *
+ * @author Andy Wilkinson
+ */
+public class ConfigurationPropertiesPlugin implements Plugin {
+
+ /**
+ * Name of the {@link Configuration} that holds the configuration property metadata
+ * artifact.
+ */
+ public static final String CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME = "configurationPropertiesMetadata";
+
+ @Override
+ public void apply(Project project) {
+ project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
+ addConfigurationProcessorDependency(project);
+ configureAdditionalMetadataLocationsCompilerArgument(project);
+ addMetadataArtifact(project);
+ });
+ }
+
+ private void addConfigurationProcessorDependency(Project project) {
+ Configuration annotationProcessors = project.getConfigurations()
+ .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
+ annotationProcessors.getDependencies().add(project.getDependencies().project(Collections.singletonMap("path",
+ ":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor")));
+ }
+
+ private void addMetadataArtifact(Project project) {
+ SourceSet mainSourceSet = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
+ .getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+ project.getConfigurations().maybeCreate(CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME);
+ project.afterEvaluate((evaluatedProject) -> evaluatedProject.getArtifacts().add(
+ CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME,
+ evaluatedProject.provider((Callable) () -> new File(mainSourceSet.getJava().getOutputDir(),
+ "META-INF/spring-configuration-metadata.json")),
+ (artifact) -> artifact
+ .builtBy(evaluatedProject.getTasks().getByName(mainSourceSet.getClassesTaskName()))));
+ }
+
+ private void configureAdditionalMetadataLocationsCompilerArgument(Project project) {
+ JavaCompile compileJava = project.getTasks().withType(JavaCompile.class)
+ .getByName(JavaPlugin.COMPILE_JAVA_TASK_NAME);
+ SourceSet mainSourceSet = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
+ .getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+ compileJava.getOptions().getCompilerArgs()
+ .add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + StringUtils
+ .collectionToCommaDelimitedString(mainSourceSet.getResources().getSourceDirectories().getFiles()
+ .stream().map(project.getRootProject()::relativePath).collect(Collectors.toSet())));
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperty.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperty.java
new file mode 100644
index 000000000000..e66c236f478f
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperty.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 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.context.properties;
+
+/**
+ * A configuration property.
+ *
+ * @author Andy Wilkinson
+ */
+public class ConfigurationProperty {
+
+ private final String name;
+
+ private final String type;
+
+ private final Object defaultValue;
+
+ private final String description;
+
+ private final boolean deprecated;
+
+ ConfigurationProperty(String name, String type) {
+ this(name, type, null, null, false);
+ }
+
+ ConfigurationProperty(String name, String type, Object defaultValue, String description, boolean deprecated) {
+ this.name = name;
+ this.type = type;
+ this.defaultValue = defaultValue;
+ this.description = description;
+ this.deprecated = deprecated;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getType() {
+ return this.type;
+ }
+
+ public Object getDefaultValue() {
+ return this.defaultValue;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public boolean isDeprecated() {
+ return this.deprecated;
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationTable.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationTable.java
similarity index 92%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationTable.java
rename to buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationTable.java
index 83a36d9bb1d2..533c0b8365d1 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationTable.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationTable.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
import java.util.Arrays;
import java.util.Set;
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationTableEntry.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationTableEntry.java
similarity index 92%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationTableEntry.java
rename to buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationTableEntry.java
index 8c81a9601738..a0ebc3361b66 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/ConfigurationTableEntry.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationTableEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
/**
* Abstract class for entries in {@link ConfigurationTable}.
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java
new file mode 100644
index 000000000000..bb38138e2cc0
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012-2020 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.context.properties;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+
+import org.springframework.boot.build.context.properties.DocumentOptions.Builder;
+
+/**
+ * {@link Task} used to document auto-configuration classes.
+ *
+ * @author Andy Wilkinson
+ */
+public class DocumentConfigurationProperties extends AbstractTask {
+
+ private FileCollection configurationPropertyMetadata;
+
+ private File outputDir;
+
+ @InputFiles
+ public FileCollection getConfigurationPropertyMetadata() {
+ return this.configurationPropertyMetadata;
+ }
+
+ public void setConfigurationPropertyMetadata(FileCollection configurationPropertyMetadata) {
+ this.configurationPropertyMetadata = configurationPropertyMetadata;
+ }
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return this.outputDir;
+ }
+
+ public void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ @TaskAction
+ void documentConfigurationProperties() throws IOException {
+ Builder builder = DocumentOptions.builder();
+ builder.addSection("core")
+ .withKeyPrefixes("debug", "trace", "logging", "spring.aop", "spring.application",
+ "spring.autoconfigure", "spring.banner", "spring.beaninfo", "spring.codec", "spring.config",
+ "spring.info", "spring.jmx", "spring.main", "spring.messages", "spring.pid", "spring.profiles",
+ "spring.quartz", "spring.reactor", "spring.task", "spring.mandatory-file-encoding", "info",
+ "spring.output.ansi.enabled")
+ .addSection("mail").withKeyPrefixes("spring.mail", "spring.sendgrid").addSection("cache")
+ .withKeyPrefixes("spring.cache").addSection("server").withKeyPrefixes("server").addSection("web")
+ .withKeyPrefixes("spring.hateoas", "spring.http", "spring.servlet", "spring.jersey", "spring.mvc",
+ "spring.resources", "spring.webflux")
+ .addSection("json").withKeyPrefixes("spring.jackson", "spring.gson").addSection("rsocket")
+ .withKeyPrefixes("spring.rsocket").addSection("templating")
+ .withKeyPrefixes("spring.freemarker", "spring.groovy", "spring.mustache", "spring.thymeleaf")
+ .addOverride("spring.groovy.template.configuration", "See GroovyMarkupConfigurer")
+ .addSection("security").withKeyPrefixes("spring.security", "spring.ldap", "spring.session")
+ .addSection("data-migration").withKeyPrefixes("spring.flyway", "spring.liquibase").addSection("data")
+ .withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx",
+ "spring.mongodb", "spring.redis", "spring.dao", "spring.data", "spring.datasource",
+ "spring.jooq", "spring.jdbc", "spring.jpa")
+ .addOverride("spring.datasource.dbcp2", "Commons DBCP2 specific settings")
+ .addOverride("spring.datasource.tomcat", "Tomcat datasource specific settings")
+ .addOverride("spring.datasource.hikari", "Hikari specific settings").addSection("transaction")
+ .withKeyPrefixes("spring.jta", "spring.transaction").addSection("integration")
+ .withKeyPrefixes("spring.activemq", "spring.artemis", "spring.batch", "spring.integration",
+ "spring.jms", "spring.kafka", "spring.rabbitmq", "spring.hazelcast", "spring.webservices")
+ .addSection("actuator").withKeyPrefixes("management").addSection("devtools")
+ .withKeyPrefixes("spring.devtools").addSection("testing").withKeyPrefixes("spring.test");
+ DocumentOptions options = builder.build();
+ new ConfigurationMetadataDocumentWriter().writeDocument(this.outputDir.toPath(), options,
+ this.configurationPropertyMetadata);
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/DocumentOptions.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentOptions.java
similarity index 95%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/DocumentOptions.java
rename to buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentOptions.java
index 6e96e8d94bb2..53fc4adec3f4 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/DocumentOptions.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentOptions.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
import java.util.Arrays;
import java.util.HashMap;
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/SingleConfigurationTableEntry.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleConfigurationTableEntry.java
similarity index 88%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/SingleConfigurationTableEntry.java
rename to buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleConfigurationTableEntry.java
index 17ac8bc32a21..a2692818433c 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/main/java/org/springframework/boot/configurationdocs/SingleConfigurationTableEntry.java
+++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleConfigurationTableEntry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
import java.util.Arrays;
import java.util.stream.Collectors;
-import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
-
/**
* Table entry containing a single configuration property.
*
@@ -32,8 +30,8 @@ class SingleConfigurationTableEntry extends ConfigurationTableEntry {
private final String defaultValue;
- SingleConfigurationTableEntry(ConfigurationMetadataProperty property) {
- this.key = property.getId();
+ SingleConfigurationTableEntry(ConfigurationProperty property) {
+ this.key = property.getName();
if (property.getType() != null && property.getType().startsWith("java.util.Map")) {
this.key += ".*";
}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/log4j2/ReproducibleLog4j2PluginsDatAction.java b/buildSrc/src/main/java/org/springframework/boot/build/log4j2/ReproducibleLog4j2PluginsDatAction.java
new file mode 100644
index 000000000000..0414fb1cbb79
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/log4j2/ReproducibleLog4j2PluginsDatAction.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012-2020 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.log4j2;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.api.tasks.compile.JavaCompile;
+
+/**
+ * An {@Action} to post-process a {@code Log4j2Plugins.dat} and re-order its content so
+ * that it is reproducible.
+ *
+ * @author Andy Wilkinson
+ */
+public class ReproducibleLog4j2PluginsDatAction implements Action {
+
+ @Override
+ public void execute(JavaCompile javaCompile) {
+ File datFile = new File(javaCompile.getDestinationDir(),
+ "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat");
+ try {
+ postProcess(datFile);
+ }
+ catch (IOException ex) {
+ throw new TaskExecutionException(javaCompile, ex);
+ }
+ }
+
+ void postProcess(File datFile) throws IOException {
+ if (!datFile.isFile()) {
+ throw new InvalidUserDataException(
+ "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat not found");
+ }
+ Map> categories = new TreeMap<>();
+ try (DataInputStream input = new DataInputStream(new FileInputStream(datFile))) {
+ int categoryCount = input.readInt();
+ for (int i = 0; i < categoryCount; i++) {
+ String categoryName = input.readUTF();
+ int pluginCount = input.readInt();
+ Map category = categories.computeIfAbsent(categoryName, (c) -> new TreeMap<>());
+ for (int j = 0; j < pluginCount; j++) {
+ Plugin plugin = new Plugin(input.readUTF(), input.readUTF(), input.readUTF(), input.readBoolean(),
+ input.readBoolean());
+ category.putIfAbsent(plugin.key, plugin);
+ }
+ }
+ }
+ try (DataOutputStream output = new DataOutputStream(new FileOutputStream(datFile))) {
+ output.writeInt(categories.size());
+ for (Entry> category : categories.entrySet()) {
+ output.writeUTF(category.getKey());
+ output.writeInt(category.getValue().size());
+ for (Plugin plugin : category.getValue().values()) {
+ output.writeUTF(plugin.key);
+ output.writeUTF(plugin.className);
+ output.writeUTF(plugin.name);
+ output.writeBoolean(plugin.printable);
+ output.writeBoolean(plugin.defer);
+ }
+ }
+ }
+ }
+
+ private static final class Plugin {
+
+ private final String key;
+
+ private final String className;
+
+ private final String name;
+
+ private final boolean printable;
+
+ private final boolean defer;
+
+ private Plugin(String key, String className, String name, boolean printable, boolean defer) {
+ this.key = key;
+ this.className = className;
+ this.name = name;
+ this.printable = printable;
+ this.defer = defer;
+ }
+
+ }
+
+}
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
new file mode 100644
index 000000000000..2a16072dbb13
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/DocumentPluginGoals.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2012-2020 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.mavenplugin;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+
+import org.springframework.boot.build.mavenplugin.PluginXmlParser.Mojo;
+import org.springframework.boot.build.mavenplugin.PluginXmlParser.Parameter;
+import org.springframework.boot.build.mavenplugin.PluginXmlParser.Plugin;
+
+/**
+ * A {@link Task} to document the plugin's goals.
+ *
+ * @author Andy Wilkinson
+ */
+public class DocumentPluginGoals extends DefaultTask {
+
+ private final PluginXmlParser parser = new PluginXmlParser();
+
+ private File pluginXml;
+
+ private File outputDir;
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return this.outputDir;
+ }
+
+ public void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ @InputFile
+ public File getPluginXml() {
+ return this.pluginXml;
+ }
+
+ public void setPluginXml(File pluginXml) {
+ this.pluginXml = pluginXml;
+ }
+
+ @TaskAction
+ public void documentPluginGoals() throws IOException {
+ Plugin plugin = this.parser.parse(this.pluginXml);
+ writeOverview(plugin);
+ for (Mojo mojo : plugin.getMojos()) {
+ documentMojo(plugin, mojo);
+ }
+ }
+
+ private void writeOverview(Plugin plugin) throws IOException {
+ try (PrintWriter writer = new PrintWriter(new FileWriter(new File(this.outputDir, "overview.adoc")))) {
+ writer.println("[cols=\"1,3\"]");
+ writer.println("|===");
+ writer.println("| Goal | Description");
+ writer.println();
+ for (Mojo mojo : plugin.getMojos()) {
+ writer.printf("| <>%n", mojo.getGoal(), plugin.getGoalPrefix(), mojo.getGoal());
+ writer.printf("| %s%n", mojo.getDescription());
+ writer.println();
+ }
+ writer.println("|===");
+ }
+ }
+
+ 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 = "goals-" + mojo.getGoal();
+ writer.println();
+ writer.println();
+ writer.printf("[[%s]]%n", sectionId);
+ writer.printf("== `%s:%s`%n", plugin.getGoalPrefix(), mojo.getGoal());
+ writer.printf("`%s:%s:%s`%n", plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion());
+ writer.println();
+ writer.println(mojo.getDescription());
+ List parameters = mojo.getParameters().stream().filter(Parameter::isEditable)
+ .collect(Collectors.toList());
+ List requiredParameters = parameters.stream().filter(Parameter::isRequired)
+ .collect(Collectors.toList());
+ String parametersSectionId = sectionId + "-parameters";
+ String detailsSectionId = parametersSectionId + "-details";
+ if (!requiredParameters.isEmpty()) {
+ writer.println();
+ writer.println();
+ writer.printf("[[%s-required]]%n", parametersSectionId);
+ writer.println("=== Required parameters");
+ writeParametersTable(writer, mojo.getGoal(), requiredParameters);
+ }
+ List optionalParameters = parameters.stream().filter((parameter) -> !parameter.isRequired())
+ .collect(Collectors.toList());
+ if (!optionalParameters.isEmpty()) {
+ writer.println();
+ writer.println();
+ writer.printf("[[%s-optional]]%n", parametersSectionId);
+ writer.println("=== Optional parameters");
+ writeParametersTable(writer, detailsSectionId, optionalParameters);
+ }
+ writer.println();
+ writer.println();
+ writer.printf("[[%s]]%n", detailsSectionId);
+ writer.println("=== Parameter details");
+ writeParameterDetails(writer, parameters, detailsSectionId);
+ }
+ }
+
+ private void writeParametersTable(PrintWriter writer, String detailsSectionId, List parameters) {
+ writer.println("[cols=\"3,2,3\"]");
+ writer.println("|===");
+ writer.println("| Name | Type | Default");
+ writer.println();
+ for (Parameter parameter : parameters) {
+ String name = parameter.getName();
+ writer.printf("| <<%s-%s,%s>>%n", detailsSectionId, name, name);
+ String type = parameter.getType();
+ if (type.lastIndexOf('.') >= 0) {
+ type = type.substring(type.lastIndexOf('.') + 1);
+ }
+ writer.printf("| `%s`%n", type);
+ String defaultValue = parameter.getDefaultValue();
+ if (defaultValue != null) {
+ writer.printf("| `%s`%n", defaultValue);
+ }
+ else {
+ writer.println("|");
+ }
+ writer.println();
+ }
+ writer.println("|===");
+ }
+
+ private void writeParameterDetails(PrintWriter writer, List parameters, String sectionId) {
+ for (Parameter parameter : parameters) {
+ String name = parameter.getName();
+ writer.println();
+ writer.println();
+ writer.printf("[[%s-%s]]%n", sectionId, name);
+ writer.printf("==== `%s`%n", name);
+ writer.println(parameter.getDescription());
+ writer.println();
+ writer.println("[cols=\"10h,90\"]");
+ writer.println("|===");
+ writer.println();
+ writeDetail(writer, "Name", name);
+ writeDetail(writer, "Type", parameter.getType());
+ writeOptionalDetail(writer, "Default value", parameter.getDefaultValue());
+ writeOptionalDetail(writer, "User property", parameter.getUserProperty());
+ writeOptionalDetail(writer, "Since", parameter.getSince());
+ writer.println("|===");
+ }
+ }
+
+ private void writeDetail(PrintWriter writer, String name, String value) {
+ writer.printf("| %s%n", name);
+ writer.printf("| `%s`%n", value);
+ writer.println();
+ }
+
+ private void writeOptionalDetail(PrintWriter writer, String name, String value) {
+ writer.printf("| %s%n", name);
+ if (value != null) {
+ writer.printf("| `%s`%n", value);
+ }
+ else {
+ writer.println("|");
+ }
+ writer.println();
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenExec.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenExec.java
new file mode 100644
index 000000000000..17694f11d141
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenExec.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012-2020 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.mavenplugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.tasks.Internal;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.process.internal.ExecException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A custom {@link JavaExec} {@link Task task} for running Maven.
+ *
+ * @author Andy Wilkinson
+ */
+public class MavenExec extends JavaExec {
+
+ private Logger log = LoggerFactory.getLogger(MavenExec.class);
+
+ private File projectDir;
+
+ public MavenExec() throws IOException {
+ setClasspath(mavenConfiguration(getProject()));
+ args("--batch-mode");
+ setMain("org.apache.maven.cli.MavenCli");
+ }
+
+ public void setProjectDir(File projectDir) {
+ this.projectDir = projectDir;
+ getInputs().file(new File(projectDir, "pom.xml"));
+ }
+
+ @Override
+ public void exec() {
+ workingDir(this.projectDir);
+ systemProperty("maven.multiModuleProjectDirectory", this.projectDir.getAbsolutePath());
+ try {
+ Path logFile = Files.createTempFile(getName(), ".log");
+ try {
+ args("--log-file", logFile.toFile().getAbsolutePath());
+ super.exec();
+ if (this.log.isInfoEnabled()) {
+ Files.readAllLines(logFile).forEach(this.log::info);
+ }
+ }
+ catch (ExecException ex) {
+ System.out.println("Exec exception! Dumping log");
+ Files.readAllLines(logFile).forEach(System.out::println);
+ throw ex;
+ }
+ }
+ catch (IOException ex) {
+ throw new TaskExecutionException(this, ex);
+ }
+ }
+
+ private Configuration mavenConfiguration(Project project) {
+ Configuration existing = project.getConfigurations().findByName("maven");
+ if (existing != null) {
+ return existing;
+ }
+ return project.getConfigurations().create("maven", (maven) -> {
+ maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-embedder:3.6.2"));
+ maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-compat:3.6.2"));
+ maven.getDependencies().add(project.getDependencies().create("org.slf4j:slf4j-simple:1.7.5"));
+ maven.getDependencies().add(
+ project.getDependencies().create("org.apache.maven.resolver:maven-resolver-connector-basic:1.4.1"));
+ maven.getDependencies().add(
+ project.getDependencies().create("org.apache.maven.resolver:maven-resolver-transport-http:1.4.1"));
+ });
+ }
+
+ @Internal
+ public File getProjectDir() {
+ return this.projectDir;
+ }
+
+}
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
new file mode 100644
index 000000000000..08874b11aaac
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/MavenPluginPlugin.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2019 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.mavenplugin;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+
+import io.spring.javaformat.formatter.FileFormatter;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.plugins.JavaLibraryPlugin;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
+import org.gradle.api.tasks.Copy;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.api.tasks.bundling.Jar;
+import org.gradle.api.tasks.javadoc.Javadoc;
+import org.gradle.external.javadoc.StandardJavadocDocletOptions;
+
+import org.springframework.boot.build.DeployedPlugin;
+import org.springframework.boot.build.MavenRepositoryPlugin;
+import org.springframework.boot.build.test.IntegrationTestPlugin;
+
+/**
+ * Plugin for building Spring Boot's Maven Plugin.
+ *
+ * @author Andy Wilkinson
+ */
+public class MavenPluginPlugin implements Plugin {
+
+ @Override
+ public void apply(Project project) {
+ project.getPlugins().apply(JavaLibraryPlugin.class);
+ project.getPlugins().apply(MavenPublishPlugin.class);
+ project.getPlugins().apply(DeployedPlugin.class);
+ project.getPlugins().apply(MavenRepositoryPlugin.class);
+ project.getPlugins().apply(IntegrationTestPlugin.class);
+ Copy populateIntTestMavenRepository = project.getTasks().create("populateIntTestMavenRepository", Copy.class);
+ populateIntTestMavenRepository.setDestinationDir(project.getBuildDir());
+ populateIntTestMavenRepository.into("int-test-maven-repository", (copy) -> {
+ copy.from(project.getConfigurations().getByName(MavenRepositoryPlugin.MAVEN_REPOSITORY_CONFIGURATION_NAME));
+ copy.from(new File(project.getBuildDir(), "maven-repository"));
+ });
+ populateIntTestMavenRepository
+ .dependsOn(project.getTasks().getByName(MavenRepositoryPlugin.PUBLISH_TO_PROJECT_REPOSITORY_TASK_NAME));
+ configurePomPackaging(project);
+ MavenExec generateHelpMojo = configureMojoGenerationTasks(project);
+ MavenExec generatePluginDescriptor = configurePluginDescriptorGenerationTasks(project, generateHelpMojo);
+ DocumentPluginGoals documentPluginGoals = project.getTasks().create("documentPluginGoals",
+ DocumentPluginGoals.class);
+ documentPluginGoals.setPluginXml(generatePluginDescriptor.getOutputs().getFiles().getSingleFile());
+ documentPluginGoals.setOutputDir(new File(project.getBuildDir(), "docs/generated/goals/"));
+ documentPluginGoals.dependsOn(generatePluginDescriptor);
+ Jar jar = (Jar) project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
+ includeDescriptorInJar(jar, generatePluginDescriptor);
+ includeHelpMojoInJar(jar, generateHelpMojo);
+ PrepareMavenBinaries prepareMavenBinaries = project.getTasks().create("prepareMavenBinaries",
+ PrepareMavenBinaries.class);
+ prepareMavenBinaries.setOutputDir(new File(project.getBuildDir(), "maven-binaries"));
+ project.getTasks().getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME).dependsOn(populateIntTestMavenRepository,
+ prepareMavenBinaries);
+ }
+
+ private void configurePomPackaging(Project project) {
+ PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
+ publishing.getPublications().withType(MavenPublication.class,
+ (mavenPublication) -> mavenPublication.pom((pom) -> pom.setPackaging("maven-plugin")));
+ }
+
+ private MavenExec configureMojoGenerationTasks(Project project) {
+ File helpMojoDir = new File(project.getBuildDir(), "help-mojo");
+ Copy helpMojoInputs = createCopyHelpMojoInputs(project, helpMojoDir);
+ MavenExec generateHelpMojo = createGenerateHelpMojo(project, helpMojoDir);
+ generateHelpMojo.dependsOn(helpMojoInputs);
+ return generateHelpMojo;
+ }
+
+ private Copy createCopyHelpMojoInputs(Project project, File mavenDir) {
+ Copy mojoInputs = project.getTasks().create("copyHelpMojoInputs", Copy.class);
+ mojoInputs.setDestinationDir(mavenDir);
+ mojoInputs.from(new File(project.getProjectDir(), "src/maven/resources/pom.xml"),
+ (sync) -> sync.filter((input) -> input.replace("{{version}}", project.getVersion().toString())));
+ return mojoInputs;
+ }
+
+ private MavenExec createGenerateHelpMojo(Project project, File mavenDir) {
+ MavenExec generateHelpMojo = project.getTasks().create("generateHelpMojo", MavenExec.class);
+ generateHelpMojo.setProjectDir(mavenDir);
+ generateHelpMojo.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.0:helpmojo");
+ generateHelpMojo.getOutputs().dir(new File(mavenDir, "target/generated-sources/plugin"));
+ return generateHelpMojo;
+ }
+
+ private MavenExec configurePluginDescriptorGenerationTasks(Project project, MavenExec generateHelpMojo) {
+ File pluginDescriptorDir = new File(project.getBuildDir(), "plugin-descriptor");
+ SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
+ SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+ File generatedHelpMojoDir = new File(project.getBuildDir(), "generated/sources/helpMojo");
+ project.getTasks().withType(Javadoc.class,
+ (javadoc) -> ((StandardJavadocDocletOptions) javadoc.getOptions()).addMultilineStringsOption("tag")
+ .setValue(Arrays.asList("goal:X", "requiresProject:X", "threadSafe:X")));
+ FormatHelpMojoSource copyFormattedHelpMojoSource = project.getTasks().create("copyFormattedHelpMojoSource",
+ FormatHelpMojoSource.class);
+ copyFormattedHelpMojoSource.setGenerator(generateHelpMojo);
+ copyFormattedHelpMojoSource.setOutputDir(generatedHelpMojoDir);
+ mainSourceSet.getAllJava().srcDir(generatedHelpMojoDir);
+ project.getTasks().getByName(mainSourceSet.getCompileJavaTaskName()).dependsOn(copyFormattedHelpMojoSource);
+ Copy pluginDescriptorInputs = createCopyPluginDescriptorInputs(project, pluginDescriptorDir, mainSourceSet);
+ pluginDescriptorInputs.dependsOn(mainSourceSet.getClassesTaskName());
+ MavenExec generatePluginDescriptor = createGeneratePluginDescriptor(project, pluginDescriptorDir);
+ generatePluginDescriptor.dependsOn(pluginDescriptorInputs);
+ return generatePluginDescriptor;
+ }
+
+ private Copy createCopyPluginDescriptorInputs(Project project, File destination, SourceSet sourceSet) {
+ Copy pluginDescriptorInputs = project.getTasks().create("copyPluginDescriptorInputs", Copy.class);
+ pluginDescriptorInputs.setDestinationDir(destination);
+ pluginDescriptorInputs.from(new File(project.getProjectDir(), "src/maven/resources/pom.xml"),
+ (sync) -> sync.filter((input) -> input.replace("{{version}}", project.getVersion().toString())));
+ pluginDescriptorInputs.from(sourceSet.getOutput().getClassesDirs(), (sync) -> sync.into("target/classes"));
+ pluginDescriptorInputs.from(sourceSet.getAllJava().getSrcDirs(), (sync) -> sync.into("src/main/java"));
+ return pluginDescriptorInputs;
+ }
+
+ private MavenExec createGeneratePluginDescriptor(Project project, File mavenDir) {
+ MavenExec generatePluginDescriptor = project.getTasks().create("generatePluginDescriptor", MavenExec.class);
+ generatePluginDescriptor.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.0:descriptor");
+ generatePluginDescriptor.getOutputs().file(new File(mavenDir, "target/classes/META-INF/maven/plugin.xml"));
+ generatePluginDescriptor.getInputs().dir(new File(mavenDir, "target/classes/org"));
+ generatePluginDescriptor.setProjectDir(mavenDir);
+ return generatePluginDescriptor;
+ }
+
+ private void includeDescriptorInJar(Jar jar, JavaExec generatePluginDescriptor) {
+ jar.from(generatePluginDescriptor, (copy) -> copy.into("META-INF/maven/"));
+ jar.dependsOn(generatePluginDescriptor);
+ }
+
+ private void includeHelpMojoInJar(Jar jar, JavaExec generateHelpMojo) {
+ jar.from(generateHelpMojo);
+ jar.dependsOn(generateHelpMojo);
+ }
+
+ public static class FormatHelpMojoSource extends DefaultTask {
+
+ private Task generator;
+
+ private File outputDir;
+
+ void setGenerator(Task generator) {
+ this.generator = generator;
+ getInputs().files(this.generator);
+ }
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return this.outputDir;
+ }
+
+ void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ @TaskAction
+ void syncAndFormat() {
+ FileFormatter fileFormatter = new FileFormatter();
+ for (File output : this.generator.getOutputs().getFiles()) {
+ fileFormatter.formatFiles(getProject().fileTree(output), StandardCharsets.UTF_8).forEach((fileEdit) -> {
+ Path relativePath = output.toPath().relativize(fileEdit.getFile().toPath());
+ Path outputLocation = this.outputDir.toPath().resolve(relativePath);
+ try {
+ Files.createDirectories(outputLocation.getParent());
+ Files.write(outputLocation, fileEdit.getFormattedContent().getBytes(StandardCharsets.UTF_8));
+ }
+ catch (Exception ex) {
+ throw new TaskExecutionException(this, ex);
+ }
+ });
+ }
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PluginXmlParser.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PluginXmlParser.java
new file mode 100644
index 000000000000..6269923df2a7
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PluginXmlParser.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2019 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.mavenplugin;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * A parser for a Maven plugin's {@code plugin.xml} file.
+ *
+ * @author Andy Wilkinson
+ */
+class PluginXmlParser {
+
+ private final XPath xpath;
+
+ PluginXmlParser() {
+ this.xpath = XPathFactory.newInstance().newXPath();
+ }
+
+ Plugin parse(File pluginXml) {
+ try {
+ Node root = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pluginXml);
+ List mojos = parseMojos(root);
+ return new Plugin(textAt("//plugin/groupId", root), textAt("//plugin/artifactId", root),
+ textAt("//plugin/version", root), textAt("//plugin/goalPrefix", root), mojos);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private String textAt(String path, Node source) throws XPathExpressionException {
+ String text = this.xpath.evaluate(path + "/text()", source);
+ return (text.length() == 0) ? null : text;
+ }
+
+ private List parseMojos(Node plugin) throws XPathExpressionException {
+ List mojos = new ArrayList();
+ for (Node mojoNode : nodesAt("//plugin/mojos/mojo", plugin)) {
+ mojos.add(new Mojo(textAt("goal", mojoNode), format(textAt("description", mojoNode)),
+ parseParameters(mojoNode)));
+ }
+ return mojos;
+ }
+
+ private Iterable nodesAt(String path, Node source) throws XPathExpressionException {
+ return IterableNodeList.of((NodeList) this.xpath.evaluate(path, source, XPathConstants.NODESET));
+ }
+
+ private List parseParameters(Node mojoNode) throws XPathExpressionException {
+ Map defaultValues = new HashMap<>();
+ Map userProperties = new HashMap<>();
+ for (Node parameterConfigurationNode : nodesAt("configuration/*", mojoNode)) {
+ String userProperty = parameterConfigurationNode.getTextContent();
+ if (userProperty != null && userProperty.length() > 0) {
+ userProperties.put(parameterConfigurationNode.getNodeName(),
+ userProperty.replace("${", "`").replace("}", "`"));
+ }
+ Node defaultValueAttribute = parameterConfigurationNode.getAttributes().getNamedItem("default-value");
+ if (defaultValueAttribute != null && defaultValueAttribute.getTextContent().length() > 0) {
+ defaultValues.put(parameterConfigurationNode.getNodeName(), defaultValueAttribute.getTextContent());
+ }
+ }
+ List parameters = new ArrayList<>();
+ for (Node parameterNode : nodesAt("parameters/parameter", mojoNode)) {
+ parameters.add(parseParameter(parameterNode, defaultValues, userProperties));
+ }
+ return parameters;
+ }
+
+ private Parameter parseParameter(Node parameterNode, Map defaultValues,
+ Map userProperties) throws XPathExpressionException {
+ Parameter parameter = new Parameter(textAt("name", parameterNode), textAt("type", parameterNode),
+ booleanAt("required", parameterNode), booleanAt("editable", parameterNode),
+ format(textAt("description", parameterNode)), defaultValues.get(textAt("name", parameterNode)),
+ userProperties.get(textAt("name", parameterNode)), textAt("since", parameterNode));
+ return parameter;
+ }
+
+ private boolean booleanAt(String path, Node node) throws XPathExpressionException {
+ return Boolean.valueOf(textAt(path, node));
+ }
+
+ private String format(String input) {
+ return input.replace("", "`").replace("
", "`").replace("<", "<").replace(">", ">")
+ .replace(" ", " ").replace("\n", " ").replace(""", "\"").replaceAll("\\{@code (.*?)\\}", "`$1`")
+ .replaceAll("\\{@link (.*?)\\}", "`$1`").replaceAll("\\{@literal (.*?)\\}", "`$1`")
+ .replaceAll("(.*?) ", "\\$1[\\$2]");
+ }
+
+ private static final class IterableNodeList implements Iterable {
+
+ private final NodeList nodeList;
+
+ private IterableNodeList(NodeList nodeList) {
+ this.nodeList = nodeList;
+ }
+
+ private static Iterable of(NodeList nodeList) {
+ return new IterableNodeList(nodeList);
+ }
+
+ @Override
+ public Iterator iterator() {
+
+ return new Iterator() {
+
+ private int index = 0;
+
+ @Override
+ public boolean hasNext() {
+ return this.index < IterableNodeList.this.nodeList.getLength();
+ }
+
+ @Override
+ public Node next() {
+ return IterableNodeList.this.nodeList.item(this.index++);
+ }
+
+ };
+ }
+
+ }
+
+ static final class Plugin {
+
+ private final String groupId;
+
+ private final String artifactId;
+
+ private final String version;
+
+ private final String goalPrefix;
+
+ private final List mojos;
+
+ private Plugin(String groupId, String artifactId, String version, String goalPrefix, List mojos) {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ this.goalPrefix = goalPrefix;
+ this.mojos = mojos;
+ }
+
+ String getGroupId() {
+ return this.groupId;
+ }
+
+ String getArtifactId() {
+ return this.artifactId;
+ }
+
+ String getVersion() {
+ return this.version;
+ }
+
+ String getGoalPrefix() {
+ return this.goalPrefix;
+ }
+
+ List getMojos() {
+ return this.mojos;
+ }
+
+ }
+
+ static final class Mojo {
+
+ private final String goal;
+
+ private final String description;
+
+ private final List parameters;
+
+ private Mojo(String goal, String description, List parameters) {
+ this.goal = goal;
+ this.description = description;
+ this.parameters = parameters;
+ }
+
+ String getGoal() {
+ return this.goal;
+ }
+
+ String getDescription() {
+ return this.description;
+ }
+
+ List getParameters() {
+ return this.parameters;
+ }
+
+ }
+
+ static final class Parameter {
+
+ private final String name;
+
+ private final String type;
+
+ private final boolean required;
+
+ private final boolean editable;
+
+ private final String description;
+
+ private final String defaultValue;
+
+ private final String userProperty;
+
+ private final String since;
+
+ private Parameter(String name, String type, boolean required, boolean editable, String description,
+ String defaultValue, String userProperty, String since) {
+ this.name = name;
+ this.type = type;
+ this.required = required;
+ this.editable = editable;
+ this.description = description;
+ this.defaultValue = defaultValue;
+ this.userProperty = userProperty;
+ this.since = since;
+ }
+
+ String getName() {
+ return this.name;
+ }
+
+ String getType() {
+ return this.type;
+ }
+
+ boolean isRequired() {
+ return this.required;
+ }
+
+ boolean isEditable() {
+ return this.editable;
+ }
+
+ String getDescription() {
+ return this.description;
+ }
+
+ String getDefaultValue() {
+ return this.defaultValue;
+ }
+
+ String getUserProperty() {
+ return this.userProperty;
+ }
+
+ String getSince() {
+ return this.since;
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PrepareMavenBinaries.java b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PrepareMavenBinaries.java
new file mode 100644
index 000000000000..e4f18ece6b49
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/mavenplugin/PrepareMavenBinaries.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 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.mavenplugin;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+
+/**
+ * {@link Task} to make Maven binaries available for integration testing.
+ *
+ * @author Andy Wilkinson
+ */
+public class PrepareMavenBinaries extends DefaultTask {
+
+ private Set versions = new LinkedHashSet<>();
+
+ private File outputDir;
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return this.outputDir;
+ }
+
+ public void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ @Input
+ public Set getVersions() {
+ return this.versions;
+ }
+
+ public void versions(String... versions) {
+ this.versions.addAll(Arrays.asList(versions));
+ }
+
+ @TaskAction
+ public void prepareBinaries() {
+ for (String version : this.versions) {
+ Configuration configuration = getProject().getConfigurations().detachedConfiguration(
+ getProject().getDependencies().create("org.apache.maven:apache-maven:" + version + ":bin@zip"));
+ getProject().copy(
+ (copy) -> copy.into(this.outputDir).from(getProject().zipTree(configuration.getSingleFile())));
+ }
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/optional/OptionalDependenciesPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/optional/OptionalDependenciesPlugin.java
new file mode 100644
index 000000000000..0f622fcee852
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/optional/OptionalDependenciesPlugin.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012-2020 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.optional;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.attributes.Usage;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.SourceSetContainer;
+import org.gradle.api.tasks.javadoc.Javadoc;
+import org.gradle.plugins.ide.eclipse.EclipsePlugin;
+import org.gradle.plugins.ide.eclipse.model.EclipseModel;
+
+/**
+ * A {@code Plugin} that adds support for Maven-style optional dependencies. Creates a new
+ * {@code optional} configuration. The {@code optional} configuration is part of the
+ * project's compile and runtime classpath's but does not affect the classpath of
+ * dependent projects.
+ *
+ * @author Andy Wilkinson
+ */
+public class OptionalDependenciesPlugin implements Plugin {
+
+ /**
+ * Name of the {@code optional} configuration.
+ */
+ public static final String OPTIONAL_CONFIGURATION_NAME = "optional";
+
+ @Override
+ public void apply(Project project) {
+ Configuration optional = project.getConfigurations().create("optional");
+ optional.attributes((attributes) -> attributes.attribute(Usage.USAGE_ATTRIBUTE,
+ project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)));
+ project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
+ SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class)
+ .getSourceSets();
+ sourceSets.all((sourceSet) -> {
+ sourceSet.setCompileClasspath(sourceSet.getCompileClasspath().plus(optional));
+ sourceSet.setRuntimeClasspath(sourceSet.getRuntimeClasspath().plus(optional));
+ });
+ project.getTasks().withType(Javadoc.class)
+ .all((javadoc) -> javadoc.setClasspath(javadoc.getClasspath().plus(optional)));
+ });
+ project.getPlugins().withType(EclipsePlugin.class,
+ (eclipePlugin) -> project.getExtensions().getByType(EclipseModel.class)
+ .classpath((classpath) -> classpath.getPlusConfigurations().add(optional)));
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java b/buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java
new file mode 100644
index 000000000000..a5cdf124d47a
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/starters/DocumentStarters.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2019 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.starters;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Task} to document all starter projects.
+ *
+ * @author Andy Wilkinson
+ */
+public class DocumentStarters extends AbstractTask {
+
+ private final Configuration starters;
+
+ private File outputDir;
+
+ public DocumentStarters() {
+ this.starters = getProject().getConfigurations().create("starters");
+ getProject().getGradle().projectsEvaluated((gradle) -> {
+ gradle.allprojects((project) -> {
+ if (project.getPlugins().hasPlugin(StarterPlugin.class)) {
+ Map dependency = new HashMap<>();
+ dependency.put("path", project.getPath());
+ dependency.put("configuration", "starterMetadata");
+ this.starters.getDependencies().add(project.getDependencies().project(dependency));
+ }
+ });
+ });
+ }
+
+ @OutputDirectory
+ public File getOutputDir() {
+ return this.outputDir;
+ }
+
+ public void setOutputDir(File outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ @InputFiles
+ public FileCollection getStarters() {
+ return this.starters;
+ }
+
+ @TaskAction
+ void documentStarters() {
+ Set starters = this.starters.getFiles().stream().map(this::loadStarter)
+ .collect(Collectors.toCollection(TreeSet::new));
+ writeTable("application-starters", starters.stream().filter(Starter::isApplication));
+ writeTable("production-starters", starters.stream().filter(Starter::isProduction));
+ writeTable("technical-starters", starters.stream().filter(Starter::isTechnical));
+ }
+
+ private Starter loadStarter(File metadata) {
+ Properties properties = new Properties();
+ try (FileReader reader = new FileReader(metadata)) {
+ properties.load(reader);
+ return new Starter(properties.getProperty("name"), properties.getProperty("description"),
+ StringUtils.commaDelimitedListToSet(properties.getProperty("dependencies")));
+ }
+ catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private void writeTable(String name, Stream starters) {
+ File output = new File(this.outputDir, name + ".adoc");
+ output.getParentFile().mkdirs();
+ try (PrintWriter writer = new PrintWriter(new FileWriter(output))) {
+ writer.println("|===");
+ writer.println("| Name | Description");
+ starters.forEach((starter) -> {
+ writer.println();
+ writer.printf("| [[%s]]`%s`%n", starter.name, starter.name);
+ writer.printf("| %s%n", postProcessDescription(starter.description));
+ });
+ writer.println("|===");
+ }
+ catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private String postProcessDescription(String description) {
+ return addStarterCrossLinks(description);
+ }
+
+ private String addStarterCrossLinks(String input) {
+ return input.replaceAll("(spring-boot-starter[A-Za-z-]*)", "<<$1,`$1`>>");
+ }
+
+ private static final class Starter implements Comparable {
+
+ private final String name;
+
+ private final String description;
+
+ private final Set dependencies;
+
+ private Starter(String name, String description, Set dependencies) {
+ this.name = name;
+ this.description = description;
+ this.dependencies = dependencies;
+ }
+
+ private boolean isProduction() {
+ return this.name.equals("spring-boot-starter-actuator");
+ }
+
+ private boolean isTechnical() {
+ return !Arrays.asList("spring-boot-starter", "spring-boot-starter-test").contains(this.name)
+ && !isProduction() && !this.dependencies.contains("spring-boot-starter");
+ }
+
+ private boolean isApplication() {
+ return !isProduction() && !isTechnical();
+ }
+
+ @Override
+ public int compareTo(Starter other) {
+ return this.name.compareTo(other.name);
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterMetadata.java
new file mode 100644
index 000000000000..75e09c127385
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterMetadata.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012-2020 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.starters;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
+
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+/**
+ * A {@link Task} for generating metadata that describes a starter.
+ *
+ * @author Andy Wilkinson
+ */
+public class StarterMetadata extends AbstractTask {
+
+ private Configuration dependencies;
+
+ private File destination;
+
+ public StarterMetadata() {
+ getInputs().property("name", (Callable) () -> getProject().getName());
+ getInputs().property("description", (Callable) () -> getProject().getDescription());
+ }
+
+ @InputFiles
+ public FileCollection getDependencies() {
+ return this.dependencies;
+ }
+
+ public void setDependencies(Configuration dependencies) {
+ this.dependencies = dependencies;
+ }
+
+ @OutputFile
+ public File getDestination() {
+ return this.destination;
+ }
+
+ public void setDestination(File destination) {
+ this.destination = destination;
+ }
+
+ @TaskAction
+ void generateMetadata() throws IOException {
+ Properties properties = new Properties();
+ properties.setProperty("name", getProject().getName());
+ properties.setProperty("description", getProject().getDescription());
+ properties.setProperty("dependencies", String.join(",", this.dependencies.getResolvedConfiguration()
+ .getResolvedArtifacts().stream().map(ResolvedArtifact::getName).collect(Collectors.toSet())));
+ this.destination.getParentFile().mkdirs();
+ try (FileWriter writer = new FileWriter(this.destination)) {
+ properties.store(writer, null);
+ }
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterPlugin.java
new file mode 100644
index 000000000000..3ad6a3f714c8
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterPlugin.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 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.starters;
+
+import java.io.File;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.plugins.JavaBasePlugin;
+import org.gradle.api.plugins.JavaLibraryPlugin;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.PluginContainer;
+
+import org.springframework.boot.build.ConventionsPlugin;
+import org.springframework.boot.build.DeployedPlugin;
+import org.springframework.boot.build.classpath.CheckClasspathForConflicts;
+import org.springframework.boot.build.classpath.CheckClasspathForProhibitedDependencies;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link Plugin} for a starter project.
+ *
+ * @author Andy Wilkinson
+ */
+public class StarterPlugin implements Plugin {
+
+ @Override
+ public void apply(Project project) {
+ PluginContainer plugins = project.getPlugins();
+ plugins.apply(DeployedPlugin.class);
+ plugins.apply(JavaLibraryPlugin.class);
+ plugins.apply(ConventionsPlugin.class);
+ StarterMetadata starterMetadata = project.getTasks().create("starterMetadata", StarterMetadata.class);
+ ConfigurationContainer configurations = project.getConfigurations();
+ Configuration runtimeClasspath = configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
+ starterMetadata.setDependencies(runtimeClasspath);
+ File destination = new File(project.getBuildDir(), "starter-metadata.properties");
+ starterMetadata.setDestination(destination);
+ configurations.create("starterMetadata");
+ project.getArtifacts().add("starterMetadata", project.provider(starterMetadata::getDestination),
+ (artifact) -> artifact.builtBy(starterMetadata));
+ createClasspathConflictsCheck(runtimeClasspath, project);
+ createProhibitedDependenciesCheck(runtimeClasspath, project);
+ }
+
+ private void createClasspathConflictsCheck(Configuration classpath, Project project) {
+ CheckClasspathForConflicts checkClasspathForConflicts = project.getTasks().create(
+ "check" + StringUtils.capitalize(classpath.getName() + "ForConflicts"),
+ CheckClasspathForConflicts.class);
+ checkClasspathForConflicts.setClasspath(classpath);
+ project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForConflicts);
+ }
+
+ private void createProhibitedDependenciesCheck(Configuration classpath, Project project) {
+ CheckClasspathForProhibitedDependencies checkClasspathForProhibitedDependencies = project.getTasks().create(
+ "check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies"),
+ CheckClasspathForProhibitedDependencies.class);
+ checkClasspathForProhibitedDependencies.setClasspath(classpath);
+ project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForProhibitedDependencies);
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/IntegrationTestPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/test/IntegrationTestPlugin.java
new file mode 100644
index 000000000000..58ac2fb33f10
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/test/IntegrationTestPlugin.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012-2020 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.test;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
+import org.gradle.api.tasks.testing.Test;
+import org.gradle.language.base.plugins.LifecycleBasePlugin;
+import org.gradle.plugins.ide.eclipse.EclipsePlugin;
+import org.gradle.plugins.ide.eclipse.model.EclipseModel;
+
+/**
+ * A {@Plugin} to configure integration testing support in a {@link Project}.
+ *
+ * @author Andy Wilkinson
+ */
+public class IntegrationTestPlugin implements Plugin {
+
+ /**
+ * Name of the {@code intTest} task.
+ */
+ public static String INT_TEST_TASK_NAME = "intTest";
+
+ /**
+ * Name of the {@code intTest} source set.
+ */
+ public static String INT_TEST_SOURCE_SET_NAME = "intTest";
+
+ @Override
+ public void apply(Project project) {
+ project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> this.configureIntegrationTesting(project));
+ }
+
+ private void configureIntegrationTesting(Project project) {
+ SourceSet intTestSourceSet = createSourceSet(project);
+ Test intTest = createTestTask(project, intTestSourceSet);
+ project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(intTest);
+ project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> {
+ EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class);
+ eclipse.classpath((classpath) -> classpath.getPlusConfigurations().add(
+ project.getConfigurations().getByName(intTestSourceSet.getRuntimeClasspathConfigurationName())));
+ });
+ }
+
+ private SourceSet createSourceSet(Project project) {
+ SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
+ SourceSet intTestSourceSet = sourceSets.create(INT_TEST_SOURCE_SET_NAME);
+ SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+ intTestSourceSet.setCompileClasspath(intTestSourceSet.getCompileClasspath().plus(main.getOutput()));
+ intTestSourceSet.setRuntimeClasspath(intTestSourceSet.getRuntimeClasspath().plus(main.getOutput()));
+ return intTestSourceSet;
+ }
+
+ private Test createTestTask(Project project, SourceSet intTestSourceSet) {
+ Test intTest = project.getTasks().create(INT_TEST_TASK_NAME, Test.class);
+ intTest.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
+ intTest.setDescription("Runs integration tests.");
+ intTest.setTestClassesDirs(intTestSourceSet.getOutput().getClassesDirs());
+ intTest.setClasspath(intTestSourceSet.getRuntimeClasspath());
+ intTest.shouldRunAfter(JavaPlugin.TEST_TASK_NAME);
+ return intTest;
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/DocumentTestSlices.java b/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/DocumentTestSlices.java
new file mode 100644
index 000000000000..6643b9fcf190
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/DocumentTestSlices.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012-2020 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.test.autoconfigure;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Task} used to document test slices.
+ *
+ * @author Andy Wilkinson
+ */
+public class DocumentTestSlices extends AbstractTask {
+
+ private FileCollection testSlices;
+
+ private File outputFile;
+
+ @InputFiles
+ public FileCollection getTestSlices() {
+ return this.testSlices;
+ }
+
+ public void setTestSlices(FileCollection testSlices) {
+ this.testSlices = testSlices;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return this.outputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ @TaskAction
+ void documentTestSlices() throws IOException {
+ Set testSlices = readTestSlices();
+ writeTable(testSlices);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Set readTestSlices() throws IOException {
+ Set testSlices = new TreeSet<>();
+ for (File metadataFile : this.testSlices) {
+ Properties metadata = new Properties();
+ try (Reader reader = new FileReader(metadataFile)) {
+ metadata.load(reader);
+ }
+ for (String name : Collections.list((Enumeration) metadata.propertyNames())) {
+ testSlices.add(new TestSlice(name,
+ new TreeSet<>(StringUtils.commaDelimitedListToSet(metadata.getProperty(name)))));
+ }
+ }
+ return testSlices;
+ }
+
+ private void writeTable(Set testSlices) throws IOException {
+ this.outputFile.getParentFile().mkdirs();
+ try (PrintWriter writer = new PrintWriter(new FileWriter(this.outputFile))) {
+ writer.println("[cols=\"d,a\"]");
+ writer.println("|===");
+ writer.println("| Test slice | Imported auto-configuration");
+ for (TestSlice testSlice : testSlices) {
+ writer.println();
+ writer.printf("| `@%s`%n", testSlice.className);
+ writer.println("| ");
+ for (String importedAutoConfiguration : testSlice.importedAutoConfigurations) {
+ writer.printf("`%s`%n", importedAutoConfiguration);
+ }
+ }
+ writer.println("|===");
+ }
+ }
+
+ private static final class TestSlice implements Comparable {
+
+ private final String className;
+
+ private final SortedSet importedAutoConfigurations;
+
+ private TestSlice(String className, SortedSet importedAutoConfigurations) {
+ this.className = ClassUtils.getShortName(className);
+ this.importedAutoConfigurations = importedAutoConfigurations;
+ }
+
+ @Override
+ public int compareTo(TestSlice other) {
+ return this.className.compareTo(other.className);
+ }
+
+ }
+
+}
diff --git a/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadata.java
new file mode 100644
index 000000000000..1a1c5ab9de1a
--- /dev/null
+++ b/buildSrc/src/main/java/org/springframework/boot/build/test/autoconfigure/TestSliceMetadata.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2012-2020 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.test.autoconfigure;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.TaskAction;
+
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link Task} for generating metadata describing a project's test slices.
+ *
+ * @author Andy Wilkinson
+ */
+public class TestSliceMetadata extends AbstractTask {
+
+ private SourceSet sourceSet;
+
+ private File outputFile;
+
+ public TestSliceMetadata() {
+ getInputs().dir((Callable) () -> this.sourceSet.getOutput().getResourcesDir());
+ getInputs().files((Callable) () -> this.sourceSet.getOutput().getClassesDirs());
+ }
+
+ public void setSourceSet(SourceSet sourceSet) {
+ this.sourceSet = sourceSet;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return this.outputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ this.outputFile = outputFile;
+ Configuration testSliceMetadata = getProject().getConfigurations().maybeCreate("testSliceMetadata");
+ getProject().getArtifacts().add(testSliceMetadata.getName(),
+ getProject().provider((Callable) this::getOutputFile), (artifact) -> artifact.builtBy(this));
+ }
+
+ @TaskAction
+ void documentTestSlices() throws IOException {
+ Properties testSlices = readTestSlices();
+ getOutputFile().getParentFile().mkdirs();
+ try (FileWriter writer = new FileWriter(getOutputFile())) {
+ testSlices.store(writer, null);
+ }
+ }
+
+ private Properties readTestSlices() throws IOException {
+ Properties testSlices = new Properties();
+ try (URLClassLoader classLoader = new URLClassLoader(
+ StreamSupport.stream(this.sourceSet.getRuntimeClasspath().spliterator(), false).map(this::toURL)
+ .toArray(URL[]::new))) {
+ MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(classLoader);
+ Properties springFactories = readSpringFactories(
+ new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories"));
+ for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) {
+ addTestSlices(testSlices, classesDir, metadataReaderFactory, springFactories);
+ }
+ }
+ return testSlices;
+ }
+
+ private URL toURL(File file) {
+ try {
+ return file.toURI().toURL();
+ }
+ catch (MalformedURLException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private Properties readSpringFactories(File file) throws IOException {
+ Properties springFactories = new Properties();
+ try (Reader in = new FileReader(file)) {
+ springFactories.load(in);
+ }
+ return springFactories;
+ }
+
+ private void addTestSlices(Properties testSlices, File classesDir, MetadataReaderFactory metadataReaderFactory,
+ Properties springFactories) throws IOException {
+ try (Stream classes = Files.walk(classesDir.toPath())) {
+ classes.filter((path) -> path.toString().endsWith("Test.class"))
+ .map((path) -> getMetadataReader(path, metadataReaderFactory))
+ .filter((metadataReader) -> metadataReader.getClassMetadata().isAnnotation())
+ .forEach((metadataReader) -> addTestSlice(testSlices, springFactories, metadataReader));
+ }
+
+ }
+
+ private MetadataReader getMetadataReader(Path path, MetadataReaderFactory metadataReaderFactory) {
+ try {
+ return metadataReaderFactory.getMetadataReader(new FileSystemResource(path));
+ }
+ catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private void addTestSlice(Properties testSlices, Properties springFactories, MetadataReader metadataReader) {
+ testSlices.setProperty(metadataReader.getClassMetadata().getClassName(),
+ StringUtils.collectionToCommaDelimitedString(
+ getImportedAutoConfiguration(springFactories, metadataReader.getAnnotationMetadata())));
+ }
+
+ private SortedSet getImportedAutoConfiguration(Properties springFactories,
+ AnnotationMetadata annotationMetadata) {
+ Stream importers = findMetaImporters(annotationMetadata);
+ if (annotationMetadata.isAnnotated("org.springframework.boot.autoconfigure.ImportAutoConfiguration")) {
+ importers = Stream.concat(importers, Stream.of(annotationMetadata.getClassName()));
+ }
+ return importers.flatMap(
+ (importer) -> StringUtils.commaDelimitedListToSet(springFactories.getProperty(importer)).stream())
+ .collect(Collectors.toCollection(TreeSet::new));
+ }
+
+ private Stream findMetaImporters(AnnotationMetadata annotationMetadata) {
+ return annotationMetadata.getAnnotationTypes().stream()
+ .filter((annotationType) -> isAutoConfigurationImporter(annotationType, annotationMetadata));
+ }
+
+ private boolean isAutoConfigurationImporter(String annotationType, AnnotationMetadata metadata) {
+ return metadata.getMetaAnnotationTypes(annotationType)
+ .contains("org.springframework.boot.autoconfigure.ImportAutoConfiguration");
+ }
+
+}
diff --git a/buildSrc/src/main/resources/effective-bom-settings.xml b/buildSrc/src/main/resources/effective-bom-settings.xml
new file mode 100644
index 000000000000..d67307453cf6
--- /dev/null
+++ b/buildSrc/src/main/resources/effective-bom-settings.xml
@@ -0,0 +1,24 @@
+
+ localRepositoryPath
+
+
+ spring-repositories
+
+ true
+
+
+
+ spring-snapshot
+ https://repo.spring.io/snapshot
+
+ true
+
+
+
+ spring-milestone
+ https://repo.spring.io/milestone
+
+
+
+
+
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/assertj/NodeAssert.java b/buildSrc/src/test/java/org/springframework/boot/build/assertj/NodeAssert.java
new file mode 100644
index 000000000000..452fafaf6fdc
--- /dev/null
+++ b/buildSrc/src/test/java/org/springframework/boot/build/assertj/NodeAssert.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012-2020 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.assertj;
+
+import java.io.File;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.assertj.core.api.AbstractAssert;
+import org.assertj.core.api.AssertProvider;
+import org.assertj.core.api.StringAssert;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+/**
+ * AssertJ {@link AssertProvider} for {@link Node} assertions.
+ *
+ * @author Andy Wilkinson
+ */
+public class NodeAssert extends AbstractAssert implements AssertProvider {
+
+ private static final DocumentBuilderFactory FACTORY = DocumentBuilderFactory.newInstance();
+
+ private final XPathFactory xpathFactory = XPathFactory.newInstance();
+
+ private final XPath xpath = this.xpathFactory.newXPath();
+
+ public NodeAssert(File xmlFile) {
+ this(read(xmlFile));
+ }
+
+ public NodeAssert(Node actual) {
+ super(actual, NodeAssert.class);
+ }
+
+ private static Document read(File xmlFile) {
+ try {
+ return FACTORY.newDocumentBuilder().parse(xmlFile);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public NodeAssert nodeAtPath(String xpath) {
+ try {
+ return new NodeAssert((Node) this.xpath.evaluate(xpath, this.actual, XPathConstants.NODE));
+ }
+ catch (XPathExpressionException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public StringAssert textAtPath(String xpath) {
+ try {
+ return new StringAssert(
+ (String) this.xpath.evaluate(xpath + "/text()", this.actual, XPathConstants.STRING));
+ }
+ catch (XPathExpressionException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public NodeAssert assertThat() {
+ return this;
+ }
+
+}
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java
new file mode 100644
index 000000000000..3bd1c2bb4a81
--- /dev/null
+++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.boot.build.bom;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.function.Consumer;
+
+import org.gradle.testkit.runner.BuildResult;
+import org.gradle.testkit.runner.GradleRunner;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import org.springframework.boot.build.DeployedPlugin;
+import org.springframework.boot.build.assertj.NodeAssert;
+import org.springframework.util.FileCopyUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link BomPlugin}.
+ *
+ * @author Andy Wilkinson
+ */
+public class BomPluginIntegrationTests {
+
+ private File projectDir;
+
+ private File buildFile;
+
+ @BeforeEach
+ public void setup(@TempDir File projectDir) throws IOException {
+ this.projectDir = projectDir;
+ this.buildFile = new File(this.projectDir, "build.gradle");
+ }
+
+ @Test
+ void libraryModulesAreIncludedInDependencyManagementOfGeneratedPom() throws IOException {
+ try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
+ out.println("plugins {");
+ out.println(" id 'org.springframework.boot.bom'");
+ out.println("}");
+ out.println("bom {");
+ out.println(" library('ActiveMQ', '5.15.10') {");
+ out.println(" group('org.apache.activemq') {");
+ out.println(" modules = [");
+ out.println(" 'activemq-amqp',");
+ out.println(" 'activemq-blueprint'");
+ out.println(" ]");
+ out.println(" }");
+ out.println(" }");
+ out.println("}");
+ }
+ generatePom((pom) -> {
+ assertThat(pom).textAtPath("//properties/activemq.version").isEqualTo("5.15.10");
+ NodeAssert dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[1]");
+ assertThat(dependency).textAtPath("groupId").isEqualTo("org.apache.activemq");
+ assertThat(dependency).textAtPath("artifactId").isEqualTo("activemq-amqp");
+ assertThat(dependency).textAtPath("version").isEqualTo("${activemq.version}");
+ assertThat(dependency).textAtPath("scope").isNullOrEmpty();
+ assertThat(dependency).textAtPath("type").isNullOrEmpty();
+ dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency[2]");
+ assertThat(dependency).textAtPath("groupId").isEqualTo("org.apache.activemq");
+ assertThat(dependency).textAtPath("artifactId").isEqualTo("activemq-blueprint");
+ assertThat(dependency).textAtPath("version").isEqualTo("${activemq.version}");
+ assertThat(dependency).textAtPath("scope").isNullOrEmpty();
+ assertThat(dependency).textAtPath("type").isNullOrEmpty();
+ });
+ }
+
+ @Test
+ void libraryPluginsAreIncludedInPluginManagementOfGeneratedPom() throws IOException {
+ try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
+ out.println("plugins {");
+ out.println(" id 'org.springframework.boot.bom'");
+ out.println("}");
+ out.println("bom {");
+ out.println(" library('Flyway', '6.0.8') {");
+ out.println(" group('org.flywaydb') {");
+ out.println(" plugins = [");
+ out.println(" 'flyway-maven-plugin'");
+ out.println(" ]");
+ out.println(" }");
+ out.println(" }");
+ out.println("}");
+ }
+ generatePom((pom) -> {
+ assertThat(pom).textAtPath("//properties/flyway.version").isEqualTo("6.0.8");
+ NodeAssert plugin = pom.nodeAtPath("//pluginManagement/plugins/plugin");
+ assertThat(plugin).textAtPath("groupId").isEqualTo("org.flywaydb");
+ assertThat(plugin).textAtPath("artifactId").isEqualTo("flyway-maven-plugin");
+ assertThat(plugin).textAtPath("version").isEqualTo("${flyway.version}");
+ assertThat(plugin).textAtPath("scope").isNullOrEmpty();
+ assertThat(plugin).textAtPath("type").isNullOrEmpty();
+ });
+ }
+
+ @Test
+ void libraryImportsAreIncludedInDependencyManagementOfGeneratedPom() throws Exception {
+ try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
+ out.println("plugins {");
+ out.println(" id 'org.springframework.boot.bom'");
+ out.println("}");
+ out.println("bom {");
+ out.println(" library('Jackson Bom', '2.10.0') {");
+ out.println(" group('com.fasterxml.jackson') {");
+ out.println(" imports = [");
+ out.println(" 'jackson-bom'");
+ out.println(" ]");
+ out.println(" }");
+ out.println(" }");
+ out.println("}");
+ }
+ generatePom((pom) -> {
+ assertThat(pom).textAtPath("//properties/jackson-bom.version").isEqualTo("2.10.0");
+ NodeAssert dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency");
+ assertThat(dependency).textAtPath("groupId").isEqualTo("com.fasterxml.jackson");
+ assertThat(dependency).textAtPath("artifactId").isEqualTo("jackson-bom");
+ assertThat(dependency).textAtPath("version").isEqualTo("${jackson-bom.version}");
+ assertThat(dependency).textAtPath("scope").isEqualTo("import");
+ assertThat(dependency).textAtPath("type").isEqualTo("pom");
+ });
+ }
+
+ @Test
+ void moduleExclusionsAreIncludedInDependencyManagementOfGeneratedPom() throws IOException {
+ try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
+ out.println("plugins {");
+ out.println(" id 'org.springframework.boot.bom'");
+ out.println("}");
+ out.println("bom {");
+ out.println(" library('MySQL', '8.0.18') {");
+ out.println(" group('mysql') {");
+ out.println(" modules = [");
+ out.println(" 'mysql-connector-java' {");
+ out.println(" exclude group: 'com.google.protobuf', module: 'protobuf-java'");
+ out.println(" }");
+ out.println(" ]");
+ out.println(" }");
+ out.println(" }");
+ out.println("}");
+ }
+ generatePom((pom) -> {
+ assertThat(pom).textAtPath("//properties/mysql.version").isEqualTo("8.0.18");
+ NodeAssert dependency = pom.nodeAtPath("//dependencyManagement/dependencies/dependency");
+ assertThat(dependency).textAtPath("groupId").isEqualTo("mysql");
+ assertThat(dependency).textAtPath("artifactId").isEqualTo("mysql-connector-java");
+ assertThat(dependency).textAtPath("version").isEqualTo("${mysql.version}");
+ assertThat(dependency).textAtPath("scope").isNullOrEmpty();
+ assertThat(dependency).textAtPath("type").isNullOrEmpty();
+ NodeAssert exclusion = dependency.nodeAtPath("exclusions/exclusion");
+ assertThat(exclusion).textAtPath("groupId").isEqualTo("com.google.protobuf");
+ assertThat(exclusion).textAtPath("artifactId").isEqualTo("protobuf-java");
+ });
+ }
+
+ private BuildResult runGradle(String... args) {
+ return GradleRunner.create().withDebug(true).withProjectDir(this.projectDir).withArguments(args)
+ .withPluginClasspath().build();
+ }
+
+ private void generatePom(Consumer consumer) {
+ runGradle(DeployedPlugin.GENERATE_POM_TASK_NAME, "-s");
+ File generatedPomXml = new File(this.projectDir, "build/publications/maven/pom-default.xml");
+ try (Reader reader = new FileReader(generatedPomXml)) {
+ System.out.println(FileCopyUtils.copyToString(reader));
+ }
+ catch (IOException ex) {
+
+ }
+ assertThat(generatedPomXml).isFile();
+ consumer.accept(new NodeAssert(generatedPomXml));
+ }
+
+}
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersionTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersionTests.java
new file mode 100644
index 000000000000..643baa221998
--- /dev/null
+++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/ArtifactVersionDependencyVersionTests.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ArtifactVersionDependencyVersion}.
+ *
+ * @author Andy Wilkinson
+ */
+public class ArtifactVersionDependencyVersionTests {
+
+ @Test
+ void parseWhenVersionIsNotAMavenVersionShouldReturnNull() {
+ assertThat(version("1.2.3.1")).isNull();
+ }
+
+ @Test
+ void parseWhenVersionIsAMavenVersionShouldReturnAVersion() {
+ assertThat(version("1.2.3")).isNotNull();
+ }
+
+ @Test
+ void isNewerThanWhenInputIsOlderMajorShouldReturnTrue() {
+ assertThat(version("2.1.2").isNewerThan(version("1.9.0"))).isTrue();
+ }
+
+ @Test
+ void isNewerThanWhenInputIsOlderMinorShouldReturnTrue() {
+ assertThat(version("2.1.2").isNewerThan(version("2.0.2"))).isTrue();
+ }
+
+ @Test
+ void isNewerThanWhenInputIsOlderPatchShouldReturnTrue() {
+ assertThat(version("2.1.2").isNewerThan(version("2.1.1"))).isTrue();
+ }
+
+ @Test
+ void isNewerThanWhenInputIsNewerMajorShouldReturnFalse() {
+ assertThat(version("2.1.2").isNewerThan(version("3.2.1"))).isFalse();
+ }
+
+ @Test
+ void isSameMajorAndNewerThanWhenMinorIsOlderShouldReturnTrue() {
+ assertThat(version("1.10.2").isSameMajorAndNewerThan(version("1.9.0"))).isTrue();
+ }
+
+ @Test
+ void isSameMajorAndNewerThanWhenMajorIsOlderShouldReturnFalse() {
+ assertThat(version("2.0.2").isSameMajorAndNewerThan(version("1.9.0"))).isFalse();
+ }
+
+ @Test
+ void isSameMajorAndNewerThanWhenPatchIsNewerShouldReturnTrue() {
+ assertThat(version("2.1.2").isSameMajorAndNewerThan(version("2.1.1"))).isTrue();
+ }
+
+ @Test
+ void isSameMajorAndNewerThanWhenMinorIsNewerShouldReturnFalse() {
+ assertThat(version("2.1.2").isSameMajorAndNewerThan(version("2.2.1"))).isFalse();
+ }
+
+ @Test
+ void isSameMajorAndNewerThanWhenMajorIsNewerShouldReturnFalse() {
+ assertThat(version("2.1.2").isSameMajorAndNewerThan(version("3.0.1"))).isFalse();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenPatchIsOlderShouldReturnTrue() {
+ assertThat(version("1.10.2").isSameMinorAndNewerThan(version("1.10.1"))).isTrue();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenMinorIsOlderShouldReturnFalse() {
+ assertThat(version("2.1.2").isSameMinorAndNewerThan(version("2.0.1"))).isFalse();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenVersionsAreTheSameShouldReturnFalse() {
+ assertThat(version("2.1.2").isSameMinorAndNewerThan(version("2.1.2"))).isFalse();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenPatchIsNewerShouldReturnFalse() {
+ assertThat(version("2.1.2").isSameMinorAndNewerThan(version("2.1.3"))).isFalse();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenMinorIsNewerShouldReturnFalse() {
+ assertThat(version("2.1.2").isSameMinorAndNewerThan(version("2.0.1"))).isFalse();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenMajorIsNewerShouldReturnFalse() {
+ assertThat(version("3.1.2").isSameMinorAndNewerThan(version("2.0.1"))).isFalse();
+ }
+
+ private ArtifactVersionDependencyVersion version(String version) {
+ return ArtifactVersionDependencyVersion.parse(version);
+ }
+
+}
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/DependencyVersionTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/DependencyVersionTests.java
new file mode 100644
index 000000000000..2e0b16d7f0f7
--- /dev/null
+++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/DependencyVersionTests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link DependencyVersion}.
+ *
+ * @author Andy Wilkinson
+ */
+public class DependencyVersionTests {
+
+ @Test
+ void parseWhenValidMavenVersionShouldReturnArtifactVersionDependencyVersion() {
+ assertThat(DependencyVersion.parse("1.2.3.Final")).isInstanceOf(ArtifactVersionDependencyVersion.class);
+ }
+
+ @Test
+ void parseWhenReleaseTrainShouldReturnReleaseTrainDependencyVersion() {
+ assertThat(DependencyVersion.parse("Ingalls-SR5")).isInstanceOf(ReleaseTrainDependencyVersion.class);
+ }
+
+ @Test
+ void parseWhenMavenLikeVersionWithNumericQualifieShouldReturnNumericQualifierDependencyVersion() {
+ assertThat(DependencyVersion.parse("1.2.3.4")).isInstanceOf(NumericQualifierDependencyVersion.class);
+ }
+
+ @Test
+ void parseWhenVersionWithLeadingZeroesShouldReturnLeadingZeroesDependencyVersion() {
+ assertThat(DependencyVersion.parse("1.4.01")).isInstanceOf(LeadingZeroesDependencyVersion.class);
+ }
+
+ @Test
+ void parseWhenVersionWithCombinedPatchAndQualifierShouldReturnCombinedPatchAndQualifierDependencyVersion() {
+ assertThat(DependencyVersion.parse("4.0.0M4")).isInstanceOf(CombinedPatchAndQualifierDependencyVersion.class);
+ }
+
+}
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/NumericQualifierDependencyVersionTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/NumericQualifierDependencyVersionTests.java
new file mode 100644
index 000000000000..5e58d3a99c27
--- /dev/null
+++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/NumericQualifierDependencyVersionTests.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link NumericQualifierDependencyVersion}.
+ *
+ * @author Andy Wilkinson
+ */
+public class NumericQualifierDependencyVersionTests {
+
+ @Test
+ void isNewerThanOnVersionWithNumericQualifierWhenInputHasNoQualifierShouldReturnTrue() {
+ assertThat(version("2.9.9.20190806").isNewerThan(DependencyVersion.parse("2.9.9"))).isTrue();
+ }
+
+ @Test
+ void isNewerThanOnVersionWithNumericQualifierWhenInputHasOlderQualifierShouldReturnTrue() {
+ assertThat(version("2.9.9.20190806").isNewerThan(version("2.9.9.20190805"))).isTrue();
+ }
+
+ @Test
+ void isNewerThanOnVersionWithNumericQualifierWhenInputHasNewerQualifierShouldReturnFalse() {
+ assertThat(version("2.9.9.20190806").isNewerThan(version("2.9.9.20190807"))).isFalse();
+ }
+
+ @Test
+ void isNewerThanOnVersionWithNumericQualifierWhenInputHasSameQualifierShouldReturnFalse() {
+ assertThat(version("2.9.9.20190806").isNewerThan(version("2.9.9.20190806"))).isFalse();
+ }
+
+ private NumericQualifierDependencyVersion version(String version) {
+ return NumericQualifierDependencyVersion.parse(version);
+ }
+
+}
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersionTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersionTests.java
new file mode 100644
index 000000000000..6010bfd07506
--- /dev/null
+++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/version/ReleaseTrainDependencyVersionTests.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.build.bom.bomr.version;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ReleaseTrainDependencyVersion}.
+ *
+ * @author Andy Wilkinson
+ */
+public class ReleaseTrainDependencyVersionTests {
+
+ @Test
+ void parsingOfANonReleaseTrainVersionReturnsNull() {
+ assertThat(version("5.1.4.RELEASE")).isNull();
+ }
+
+ @Test
+ void parsingOfAReleaseTrainVersionReturnsVersion() {
+ assertThat(version("Lovelace-SR3")).isNotNull();
+ }
+
+ @Test
+ void isNewerThanWhenReleaseTrainIsNewerShouldReturnTrue() {
+ assertThat(version("Lovelace-RELEASE").isNewerThan(version("Kay-SR5"))).isTrue();
+ }
+
+ @Test
+ void isNewerThanWhenVersionIsNewerShouldReturnTrue() {
+ assertThat(version("Kay-SR10").isNewerThan(version("Kay-SR5"))).isTrue();
+ }
+
+ @Test
+ void isNewerThanWhenVersionIsOlderShouldReturnFalse() {
+ assertThat(version("Kay-RELEASE").isNewerThan(version("Kay-SR5"))).isFalse();
+ }
+
+ @Test
+ void isNewerThanWhenReleaseTrainIsOlderShouldReturnFalse() {
+ assertThat(version("Ingalls-RELEASE").isNewerThan(version("Kay-SR5"))).isFalse();
+ }
+
+ @Test
+ void isSameMajorAndNewerWhenWhenReleaseTrainIsNewerShouldReturnTrue() {
+ assertThat(version("Lovelace-RELEASE").isSameMajorAndNewerThan(version("Kay-SR5"))).isTrue();
+ }
+
+ @Test
+ void isSameMajorAndNewerThanWhenReleaseTrainIsOlderShouldReturnFalse() {
+ assertThat(version("Ingalls-RELEASE").isSameMajorAndNewerThan(version("Kay-SR5"))).isFalse();
+ }
+
+ @Test
+ void isSameMajorAndNewerThanWhenVersionIsNewerShouldReturnTrue() {
+ assertThat(version("Kay-SR6").isSameMajorAndNewerThan(version("Kay-SR5"))).isTrue();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenReleaseTrainIsNewerShouldReturnFalse() {
+ assertThat(version("Lovelace-RELEASE").isSameMinorAndNewerThan(version("Kay-SR5"))).isFalse();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenReleaseTrainIsTheSameAndVersionIsNewerShouldReturnTrue() {
+ assertThat(version("Kay-SR6").isSameMinorAndNewerThan(version("Kay-SR5"))).isTrue();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenReleaseTrainAndVersionAreTheSameShouldReturnFalse() {
+ assertThat(version("Kay-SR6").isSameMinorAndNewerThan(version("Kay-SR6"))).isFalse();
+ }
+
+ @Test
+ void isSameMinorAndNewerThanWhenReleaseTrainIsTheSameAndVersionIsOlderShouldReturnFalse() {
+ assertThat(version("Kay-SR6").isSameMinorAndNewerThan(version("Kay-SR7"))).isFalse();
+ }
+
+ private static ReleaseTrainDependencyVersion version(String input) {
+ return ReleaseTrainDependencyVersion.parse(input);
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/CompoundConfigurationTableEntryTests.java b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/CompoundConfigurationTableEntryTests.java
similarity index 65%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/CompoundConfigurationTableEntryTests.java
rename to buildSrc/src/test/java/org/springframework/boot/build/context/properties/CompoundConfigurationTableEntryTests.java
index 0e06b5950992..2f229b93b75f 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/CompoundConfigurationTableEntryTests.java
+++ b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/CompoundConfigurationTableEntryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
-import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
-
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -27,21 +25,15 @@
*
* @author Brian Clozel
*/
-class CompoundConfigurationTableEntryTests {
+public class CompoundConfigurationTableEntryTests {
private static String NEWLINE = System.lineSeparator();
@Test
void simpleProperty() {
- ConfigurationMetadataProperty firstProp = new ConfigurationMetadataProperty();
- firstProp.setId("spring.test.first");
- firstProp.setType("java.lang.String");
- ConfigurationMetadataProperty secondProp = new ConfigurationMetadataProperty();
- secondProp.setId("spring.test.second");
- secondProp.setType("java.lang.String");
- ConfigurationMetadataProperty thirdProp = new ConfigurationMetadataProperty();
- thirdProp.setId("spring.test.third");
- thirdProp.setType("java.lang.String");
+ ConfigurationProperty firstProp = new ConfigurationProperty("spring.test.first", "java.lang.String");
+ ConfigurationProperty secondProp = new ConfigurationProperty("spring.test.second", "java.lang.String");
+ ConfigurationProperty thirdProp = new ConfigurationProperty("spring.test.third", "java.lang.String");
CompoundConfigurationTableEntry entry = new CompoundConfigurationTableEntry("spring.test",
"This is a description.");
entry.addConfigurationKeys(firstProp, secondProp, thirdProp);
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/ConfigurationTableTests.java b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/ConfigurationTableTests.java
similarity index 67%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/ConfigurationTableTests.java
rename to buildSrc/src/test/java/org/springframework/boot/build/context/properties/ConfigurationTableTests.java
index 2b15a4767584..c757c73f16e9 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/ConfigurationTableTests.java
+++ b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/ConfigurationTableTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
-import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
-
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -27,23 +25,17 @@
*
* @author Brian Clozel
*/
-class ConfigurationTableTests {
+public class ConfigurationTableTests {
private static String NEWLINE = System.lineSeparator();
@Test
void simpleTable() {
ConfigurationTable table = new ConfigurationTable("test");
- ConfigurationMetadataProperty first = new ConfigurationMetadataProperty();
- first.setId("spring.test.prop");
- first.setDefaultValue("something");
- first.setDescription("This is a description.");
- first.setType("java.lang.String");
- ConfigurationMetadataProperty second = new ConfigurationMetadataProperty();
- second.setId("spring.test.other");
- second.setDefaultValue("other value");
- second.setDescription("This is another description.");
- second.setType("java.lang.String");
+ ConfigurationProperty first = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something",
+ "This is a description.", false);
+ ConfigurationProperty second = new ConfigurationProperty("spring.test.other", "java.lang.String", "other value",
+ "This is another description.", false);
table.addEntry(new SingleConfigurationTableEntry(first));
table.addEntry(new SingleConfigurationTableEntry(second));
assertThat(table.toAsciidocTable()).isEqualTo("[cols=\"1,1,2\", options=\"header\"]" + NEWLINE + "|==="
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/SingleConfigurationTableEntryTests.java b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleConfigurationTableEntryTests.java
similarity index 63%
rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/SingleConfigurationTableEntryTests.java
rename to buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleConfigurationTableEntryTests.java
index 013320c729b3..615cdca6aee1 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-docs/src/test/java/org/springframework/boot/configurationdocs/SingleConfigurationTableEntryTests.java
+++ b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleConfigurationTableEntryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2012-2020 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.
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package org.springframework.boot.configurationdocs;
+package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
-import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
-
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -27,17 +25,14 @@
*
* @author Brian Clozel
*/
-class SingleConfigurationTableEntryTests {
+public class SingleConfigurationTableEntryTests {
private static String NEWLINE = System.lineSeparator();
@Test
void simpleProperty() {
- ConfigurationMetadataProperty property = new ConfigurationMetadataProperty();
- property.setId("spring.test.prop");
- property.setDefaultValue("something");
- property.setDescription("This is a description.");
- property.setType("java.lang.String");
+ ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something",
+ "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
@@ -47,10 +42,8 @@ void simpleProperty() {
@Test
void noDefaultValue() {
- ConfigurationMetadataProperty property = new ConfigurationMetadataProperty();
- property.setId("spring.test.prop");
- property.setDescription("This is a description.");
- property.setType("java.lang.String");
+ ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null,
+ "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
@@ -60,11 +53,8 @@ void noDefaultValue() {
@Test
void defaultValueWithPipes() {
- ConfigurationMetadataProperty property = new ConfigurationMetadataProperty();
- property.setId("spring.test.prop");
- property.setDefaultValue("first|second");
- property.setDescription("This is a description.");
- property.setType("java.lang.String");
+ ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String",
+ "first|second", "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
@@ -74,11 +64,8 @@ void defaultValueWithPipes() {
@Test
void defaultValueWithBackslash() {
- ConfigurationMetadataProperty property = new ConfigurationMetadataProperty();
- property.setId("spring.test.prop");
- property.setDefaultValue("first\\second");
- property.setDescription("This is a description.");
- property.setType("java.lang.String");
+ ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String",
+ "first\\second", "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
@@ -88,10 +75,8 @@ void defaultValueWithBackslash() {
@Test
void descriptionWithPipe() {
- ConfigurationMetadataProperty property = new ConfigurationMetadataProperty();
- property.setId("spring.test.prop");
- property.setDescription("This is a description with a | pipe.");
- property.setType("java.lang.String");
+ ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null,
+ "This is a description with a | pipe.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
@@ -101,10 +86,8 @@ void descriptionWithPipe() {
@Test
void mapProperty() {
- ConfigurationMetadataProperty property = new ConfigurationMetadataProperty();
- property.setId("spring.test.prop");
- property.setDescription("This is a description.");
- property.setType("java.util.Map");
+ ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
+ "java.util.Map", null, "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
@@ -115,11 +98,8 @@ void mapProperty() {
@Test
void listProperty() {
String[] defaultValue = new String[] { "first", "second", "third" };
- ConfigurationMetadataProperty property = new ConfigurationMetadataProperty();
- property.setId("spring.test.prop");
- property.setDescription("This is a description.");
- property.setType("java.util.List");
- property.setDefaultValue(defaultValue);
+ ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
+ "java.util.List", defaultValue, "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/log4j2/ReproduciblePluginsDatActionTests.java b/buildSrc/src/test/java/org/springframework/boot/build/log4j2/ReproduciblePluginsDatActionTests.java
new file mode 100644
index 000000000000..f482f5353e96
--- /dev/null
+++ b/buildSrc/src/test/java/org/springframework/boot/build/log4j2/ReproduciblePluginsDatActionTests.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2019 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.log4j2;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.logging.log4j.core.config.plugins.processor.PluginCache;
+import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ReproducibleLog4j2PluginsDatAction}
+ *
+ * @author Andy Wilkinson
+ */
+public class ReproduciblePluginsDatActionTests {
+
+ @Test
+ void postProcessingOrdersCategoriesAndPlugins() throws IOException {
+ Path datFile = Files.createTempFile("Log4j2Plugins", "dat");
+ try {
+ write(datFile);
+ PluginCache cache = new PluginCache();
+ cache.loadCacheFiles(new Vector<>(Arrays.asList(datFile.toUri().toURL())).elements());
+ assertThat(cache.getAllCategories().keySet()).containsExactly("one", "two");
+ assertThat(cache.getCategory("one").keySet()).containsExactly("alpha", "bravo", "charlie");
+ assertThat(cache.getCategory("two").keySet()).containsExactly("delta", "echo", "foxtrot");
+ }
+ finally {
+ Files.delete(datFile);
+ }
+ }
+
+ private void write(Path datFile) throws IOException {
+ PluginCache cache = new PluginCache();
+ createCategory(cache, "two", Arrays.asList("delta", "foxtrot", "echo"));
+ createCategory(cache, "one", Arrays.asList("bravo", "alpha", "charlie"));
+ try (OutputStream output = new FileOutputStream(datFile.toFile())) {
+ cache.writeCache(output);
+ new ReproducibleLog4j2PluginsDatAction().postProcess(datFile.toFile());
+ }
+ }
+
+ private void createCategory(PluginCache cache, String categoryName, List entryNames) {
+ Map category = cache.getCategory(categoryName);
+ for (String entryName : entryNames) {
+ PluginEntry entry = new PluginEntry();
+ entry.setKey(entryName);
+ entry.setClassName("com.example.Plugin");
+ entry.setName("name");
+ entry.setCategory(categoryName);
+ category.put(entryName, entry);
+ }
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/ExampleController.java b/buildSrc/src/test/java/org/springframework/boot/build/mavenplugin/PluginXmlParserTests.java
similarity index 52%
rename from spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/ExampleController.java
rename to buildSrc/src/test/java/org/springframework/boot/build/mavenplugin/PluginXmlParserTests.java
index 6c0cbe3ca0b4..b5a4f8382041 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/ExampleController.java
+++ b/buildSrc/src/test/java/org/springframework/boot/build/mavenplugin/PluginXmlParserTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 the original author or authors.
+ * Copyright 2019 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.
@@ -14,24 +14,27 @@
* limitations under the License.
*/
-package org.springframework.boot.load.it.war;
+package org.springframework.boot.build.mavenplugin;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
+import java.io.File;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.build.mavenplugin.PluginXmlParser.Plugin;
/**
- * Simple example Spring MVC Controller.
+ * Tests for {@link PluginXmlParser}.
*
- * @author Phillip Webb
+ * @author Andy Wilkinson
*/
-@Controller
-public class ExampleController {
+public class PluginXmlParserTests {
+
+ private final PluginXmlParser parser = new PluginXmlParser();
- @RequestMapping("/")
- @ResponseBody
- public String helloWorld() {
- return "Hello Embedded WAR World!";
+ @Test
+ void dunno() {
+ Plugin plugin = this.parser.parse(new File("src/test/resources/plugin.xml"));
+ System.out.println(plugin);
}
}
diff --git a/buildSrc/src/test/java/org/springframework/boot/build/optional/OptionalDependenciesPluginIntegrationTests.java b/buildSrc/src/test/java/org/springframework/boot/build/optional/OptionalDependenciesPluginIntegrationTests.java
new file mode 100644
index 000000000000..d5585ee9e6c9
--- /dev/null
+++ b/buildSrc/src/test/java/org/springframework/boot/build/optional/OptionalDependenciesPluginIntegrationTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2012-2020 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.optional;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import org.gradle.testkit.runner.BuildResult;
+import org.gradle.testkit.runner.GradleRunner;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration tests for {@link OptionalDependenciesPlugin}.
+ *
+ * @author Andy Wilkinson
+ */
+public class OptionalDependenciesPluginIntegrationTests {
+
+ private File projectDir;
+
+ private File buildFile;
+
+ @BeforeEach
+ public void setup(@TempDir File projectDir) throws IOException {
+ this.projectDir = projectDir;
+ this.buildFile = new File(this.projectDir, "build.gradle");
+ }
+
+ @Test
+ void optionalConfigurationIsCreated() throws IOException {
+ try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
+ out.println("plugins { id 'org.springframework.boot.optional-dependencies' }");
+ out.println("task printConfigurations {");
+ out.println(" doLast {");
+ out.println(" configurations.all { println it.name }");
+ out.println(" }");
+ out.println("}");
+ }
+ BuildResult buildResult = runGradle("printConfigurations");
+ assertThat(buildResult.getOutput()).contains("optional");
+ }
+
+ @Test
+ void optionalDependenciesAreAddedToMainSourceSetsCompileClasspath() throws IOException {
+ optionalDependenciesAreAddedToSourceSetClasspath("main", "compileClasspath");
+ }
+
+ @Test
+ void optionalDependenciesAreAddedToMainSourceSetsRuntimeClasspath() throws IOException {
+ optionalDependenciesAreAddedToSourceSetClasspath("main", "runtimeClasspath");
+ }
+
+ @Test
+ void optionalDependenciesAreAddedToTestSourceSetsCompileClasspath() throws IOException {
+ optionalDependenciesAreAddedToSourceSetClasspath("test", "compileClasspath");
+ }
+
+ @Test
+ void optionalDependenciesAreAddedToTestSourceSetsRuntimeClasspath() throws IOException {
+ optionalDependenciesAreAddedToSourceSetClasspath("test", "runtimeClasspath");
+ }
+
+ public void optionalDependenciesAreAddedToSourceSetClasspath(String sourceSet, String classpath)
+ throws IOException {
+ try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
+ out.println("plugins {");
+ out.println(" id 'org.springframework.boot.optional-dependencies'");
+ out.println(" id 'java'");
+ out.println("}");
+ out.println("repositories {");
+ out.println(" mavenCentral()");
+ out.println("}");
+ out.println("dependencies {");
+ out.println(" optional 'org.springframework:spring-jcl:5.1.2.RELEASE'");
+ out.println("}");
+ out.println("task printClasspath {");
+ out.println(" doLast {");
+ out.println(" println sourceSets." + sourceSet + "." + classpath + ".files");
+ out.println(" }");
+ out.println("}");
+ }
+ BuildResult buildResult = runGradle("printClasspath");
+ assertThat(buildResult.getOutput()).contains("spring-jcl");
+ }
+
+ private BuildResult runGradle(String... args) {
+ return GradleRunner.create().withProjectDir(this.projectDir).withArguments(args).withPluginClasspath().build();
+ }
+
+}
diff --git a/buildSrc/src/test/resources/plugin.xml b/buildSrc/src/test/resources/plugin.xml
new file mode 100644
index 000000000000..f9464b5a5dd2
--- /dev/null
+++ b/buildSrc/src/test/resources/plugin.xml
@@ -0,0 +1,911 @@
+
+
+
+
+
+ Spring Boot Maven Plugin
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.2.0.GRADLE-SNAPSHOT
+ spring-boot
+ false
+ true
+
+
+ build-info
+ Generate a {@code build-info.properties} file based the content of the current
+{@link MavenProject}.
+ false
+ true
+ false
+ false
+ false
+ true
+ generate-resources
+ org.springframework.boot.maven.BuildInfoMojo
+ java
+ per-lookup
+ once-per-session
+ 1.4.0
+ true
+
+
+ additionalProperties
+ java.util.Map
+ false
+ true
+ Additional properties to store in the build-info.properties. Each entry is prefixed
+by {@code build.} in the generated build-info.properties.
+
+
+ outputFile
+ java.io.File
+ false
+ true
+ The location of the generated build-info.properties.
+
+
+ project
+ org.apache.maven.project.MavenProject
+ true
+ false
+ The Maven project.
+
+
+ session
+ org.apache.maven.execution.MavenSession
+ true
+ false
+ The Maven session.
+
+
+ time
+ java.lang.String
+ 2.2.0
+ false
+ true
+ The value used for the {@code build.time} property in a form suitable for
+{@link Instant#parse(CharSequence)}. Defaults to {@code session.request.startTime}.
+To disable the {@code build.time} property entirely, use {@code 'off'}.
+
+
+
+
+
+
+
+
+
+ org.sonatype.plexus.build.incremental.BuildContext
+ buildContext
+
+
+
+
+ help
+ Display help information on spring-boot-maven-plugin.<br>
+Call <code>mvn spring-boot:help -Ddetail=true -Dgoal=<goal-name></code> to display parameter details.
+ false
+ false
+ false
+ false
+ false
+ true
+ org.springframework.boot.maven.HelpMojo
+ java
+ per-lookup
+ once-per-session
+ true
+
+
+ detail
+ boolean
+ false
+ true
+ If <code>true</code>, display all settable properties for each goal.
+
+
+ goal
+ java.lang.String
+ false
+ true
+ The name of the goal for which to show help. If unspecified, all goals will be displayed.
+
+
+ indentSize
+ int
+ false
+ true
+ The number of spaces per indentation level, should be positive.
+
+
+ lineLength
+ int
+ false
+ true
+ The maximum length of a display line, should be positive.
+
+
+
+ ${detail}
+ ${goal}
+ ${indentSize}
+ ${lineLength}
+
+
+
+ repackage
+ Repackages existing JAR and WAR archives so that they can be executed from the command
+line using {@literal java -jar}. With <code>layout=NONE</code> can also be used simply
+to package a JAR with nested dependencies (and no main class, so not executable).
+ compile+runtime
+ false
+ true
+ false
+ false
+ false
+ true
+ package
+ org.springframework.boot.maven.RepackageMojo
+ java
+ per-lookup
+ once-per-session
+ 1.0.0
+ compile+runtime
+ true
+
+
+ attach
+ boolean
+ 1.4.0
+ false
+ true
+ Attach the repackaged archive to be installed and deployed.
+
+
+ classifier
+ java.lang.String
+ 1.0.0
+ false
+ true
+ Classifier to add to the repackaged archive. If not given, the main artifact will
+be replaced by the repackaged archive. If given, the classifier will also be used
+to determine the source archive to repackage: if an artifact with that classifier
+already exists, it will be used as source and replaced. If no such artifact exists,
+the main artifact will be used as source and the repackaged archive will be
+attached as a supplemental artifact with that classifier. Attaching the artifact
+allows to deploy it alongside to the original one, see <a href=
+"https://maven.apache.org/plugins/maven-deploy-plugin/examples/deploying-with-classifiers.html"
+>the Maven documentation for more details</a>.
+
+
+ embeddedLaunchScript
+ java.io.File
+ 1.3.0
+ false
+ true
+ The embedded launch script to prepend to the front of the jar if it is fully
+executable. If not specified the 'Spring Boot' default script will be used.
+
+
+ embeddedLaunchScriptProperties
+ java.util.Properties
+ 1.3.0
+ false
+ true
+ Properties that should be expanded in the embedded launch script.
+
+
+ excludeDevtools
+ boolean
+ 1.3.0
+ false
+ true
+ Exclude Spring Boot devtools from the repackaged archive.
+
+
+ excludeGroupIds
+ java.lang.String
+ 1.1.0
+ false
+ true
+ Comma separated list of groupId names to exclude (exact match).
+
+
+ excludes
+ java.util.List
+ 1.1.0
+ false
+ true
+ Collection of artifact definitions to exclude. The {@link Exclude} element defines
+a {@code groupId} and {@code artifactId} mandatory properties and an optional
+{@code classifier} property.
+
+
+ executable
+ boolean
+ 1.3.0
+ false
+ true
+ Make a fully executable jar for *nix machines by prepending a launch script to the
+jar.
+<p>
+Currently, some tools do not accept this format so you may not always be able to
+use this technique. For example, {@code jar -xf} may silently fail to extract a jar
+or war that has been made fully-executable. It is recommended that you only enable
+this option if you intend to execute it directly, rather than running it with
+{@code java -jar} or deploying it to a servlet container.
+
+
+ finalName
+ java.lang.String
+ 1.0.0
+ false
+ false
+ Name of the generated archive.
+
+
+ includeSystemScope
+ boolean
+ 1.4.0
+ false
+ true
+ Include system scoped dependencies.
+
+
+ includes
+ java.util.List
+ 1.2.0
+ false
+ true
+ Collection of artifact definitions to include. The {@link Include} element defines
+a {@code groupId} and {@code artifactId} mandatory properties and an optional
+{@code classifier} property.
+
+
+ layout
+ org.springframework.boot.maven.RepackageMojo$LayoutType
+ 1.0.0
+ false
+ true
+ The type of archive (which corresponds to how the dependencies are laid out inside
+it). Possible values are JAR, WAR, ZIP, DIR, NONE. Defaults to a guess based on the
+archive type.
+
+
+ layoutFactory
+ org.springframework.boot.loader.tools.LayoutFactory
+ 1.5.0
+ false
+ true
+ The layout factory that will be used to create the executable archive if no
+explicit layout is set. Alternative layouts implementations can be provided by 3rd
+parties.
+
+
+ mainClass
+ java.lang.String
+ 1.0.0
+ false
+ true
+ The name of the main class. If not specified the first compiled class found that
+contains a 'main' method will be used.
+
+
+ outputDirectory
+ java.io.File
+ 1.0.0
+ true
+ true
+ Directory containing the generated archive.
+
+
+ project
+ org.apache.maven.project.MavenProject
+ 1.0.0
+ true
+ false
+ The Maven project.
+
+
+ requiresUnpack
+ java.util.List
+ 1.1.0
+ false
+ true
+ A list of the libraries that must be unpacked from fat jars in order to run.
+Specify each library as a {@code <dependency>} with a {@code <groupId>} and a
+{@code <artifactId>} and they will be unpacked at runtime.
+
+
+ skip
+ boolean
+ 1.2.0
+ false
+ true
+ Skip the execution.
+
+
+
+
+ ${spring-boot.repackage.excludeDevtools}
+ ${spring-boot.excludeGroupIds}
+ ${spring-boot.excludes}
+
+
+
+ ${spring-boot.includes}
+ ${spring-boot.repackage.layout}
+
+
+ ${spring-boot.repackage.skip}
+
+
+
+ org.apache.maven.project.MavenProjectHelper
+ projectHelper
+
+
+
+
+ run
+ Run an executable archive application.
+ test
+ false
+ true
+ false
+ false
+ false
+ true
+ validate
+ test-compile
+ org.springframework.boot.maven.RunMojo
+ java
+ per-lookup
+ once-per-session
+ 1.0.0
+ false
+
+
+ addResources
+ boolean
+ 1.0.0
+ false
+ true
+ Add maven resources to the classpath directly, this allows live in-place editing of
+resources. Duplicate resources are removed from {@code target/classes} to prevent
+them to appear twice if {@code ClassLoader.getResources()} is called. Please
+consider adding {@code spring-boot-devtools} to your project instead as it provides
+this feature and many more.
+
+
+ agent
+ java.io.File[]
+ 1.0.0
+ since 2.2.0 in favor of {@code agents}
+ false
+ true
+ Path to agent jar. NOTE: a forked process is required to use this feature.
+
+
+ agents
+ java.io.File[]
+ 2.2.0
+ false
+ true
+ Path to agent jars. NOTE: a forked process is required to use this feature.
+
+
+ arguments
+ java.lang.String[]
+ 1.0.0
+ false
+ true
+ Arguments that should be passed to the application. On command line use commas to
+separate multiple arguments.
+
+
+ classesDirectory
+ java.io.File
+ 1.0.0
+ true
+ true
+ Directory containing the classes and resource files that should be packaged into
+the archive.
+
+
+ environmentVariables
+ java.util.Map
+ 2.1.0
+ false
+ true
+ List of Environment variables that should be associated with the forked process
+used to run the application. NOTE: a forked process is required to use this
+feature.
+
+
+ excludeGroupIds
+ java.lang.String
+ 1.1.0
+ false
+ true
+ Comma separated list of groupId names to exclude (exact match).
+
+
+ excludes
+ java.util.List
+ 1.1.0
+ false
+ true
+ Collection of artifact definitions to exclude. The {@link Exclude} element defines
+a {@code groupId} and {@code artifactId} mandatory properties and an optional
+{@code classifier} property.
+
+
+ folders
+ java.lang.String[]
+ 1.0.0
+ false
+ true
+ Additional folders besides the classes directory that should be added to the
+classpath.
+
+
+ fork
+ boolean
+ 1.2.0
+ false
+ true
+ Flag to indicate if the run processes should be forked. Disabling forking will
+disable some features such as an agent, custom JVM arguments, devtools or
+specifying the working directory to use.
+
+
+ includes
+ java.util.List
+ 1.2.0
+ false
+ true
+ Collection of artifact definitions to include. The {@link Include} element defines
+a {@code groupId} and {@code artifactId} mandatory properties and an optional
+{@code classifier} property.
+
+
+ jvmArguments
+ java.lang.String
+ 1.1.0
+ false
+ true
+ JVM arguments that should be associated with the forked process used to run the
+application. On command line, make sure to wrap multiple values between quotes.
+NOTE: a forked process is required to use this feature.
+
+
+ mainClass
+ java.lang.String
+ 1.0.0
+ false
+ true
+ The name of the main class. If not specified the first compiled class found that
+contains a 'main' method will be used.
+
+
+ noverify
+ boolean
+ 1.0.0
+ false
+ true
+ Flag to say that the agent requires -noverify.
+
+
+ optimizedLaunch
+ boolean
+ 2.2.0
+ false
+ true
+ Whether the JVM's launch should be optimized.
+
+
+ profiles
+ java.lang.String[]
+ 1.3.0
+ false
+ true
+ The spring profiles to activate. Convenience shortcut of specifying the
+'spring.profiles.active' argument. On command line use commas to separate multiple
+profiles.
+
+
+ project
+ org.apache.maven.project.MavenProject
+ 1.0.0
+ true
+ false
+ The Maven project.
+
+
+ skip
+ boolean
+ 1.3.2
+ false
+ true
+ Skip the execution.
+
+
+ systemPropertyVariables
+ java.util.Map
+ 2.1.0
+ false
+ true
+ List of JVM system properties to pass to the process. NOTE: a forked process is
+required to use this feature.
+
+
+ useTestClasspath
+ java.lang.Boolean
+ 1.3.0
+ false
+ true
+ Flag to include the test classpath when running.
+
+
+ workingDirectory
+ java.io.File
+ 1.5.0
+ false
+ true
+ Current working directory to use for the application. If not specified, basedir
+will be used. NOTE: a forked process is required to use this feature.
+
+
+
+ ${spring-boot.run.addResources}
+ ${spring-boot.run.agent}
+ ${spring-boot.run.agents}
+ ${spring-boot.run.arguments}
+
+ ${spring-boot.excludeGroupIds}
+ ${spring-boot.excludes}
+ ${spring-boot.run.folders}
+ ${spring-boot.run.fork}
+ ${spring-boot.includes}
+ ${spring-boot.run.jvmArguments}
+ ${spring-boot.run.main-class}
+ ${spring-boot.run.noverify}
+ ${spring-boot.run.optimizedLaunch}
+ ${spring-boot.run.profiles}
+
+ ${spring-boot.run.skip}
+ ${spring-boot.run.useTestClasspath}
+ ${spring-boot.run.workingDirectory}
+
+
+
+ start
+ Start a spring application. Contrary to the {@code run} goal, this does not block and
+allows other goal to operate on the application. This goal is typically used in
+integration test scenario where the application is started before a test suite and
+stopped after.
+ test
+ false
+ true
+ false
+ false
+ false
+ true
+ pre-integration-test
+ org.springframework.boot.maven.StartMojo
+ java
+ per-lookup
+ once-per-session
+ 1.3.0
+ false
+
+
+ addResources
+ boolean
+ 1.0.0
+ false
+ true
+ Add maven resources to the classpath directly, this allows live in-place editing of
+resources. Duplicate resources are removed from {@code target/classes} to prevent
+them to appear twice if {@code ClassLoader.getResources()} is called. Please
+consider adding {@code spring-boot-devtools} to your project instead as it provides
+this feature and many more.
+
+
+ agent
+ java.io.File[]
+ 1.0.0
+ since 2.2.0 in favor of {@code agents}
+ false
+ true
+ Path to agent jar. NOTE: a forked process is required to use this feature.
+
+
+ agents
+ java.io.File[]
+ 2.2.0
+ false
+ true
+ Path to agent jars. NOTE: a forked process is required to use this feature.
+
+
+ arguments
+ java.lang.String[]
+ 1.0.0
+ false
+ true
+ Arguments that should be passed to the application. On command line use commas to
+separate multiple arguments.
+
+
+ classesDirectory
+ java.io.File
+ 1.0.0
+ true
+ true
+ Directory containing the classes and resource files that should be packaged into
+the archive.
+
+
+ environmentVariables
+ java.util.Map
+ 2.1.0
+ false
+ true
+ List of Environment variables that should be associated with the forked process
+used to run the application. NOTE: a forked process is required to use this
+feature.
+
+
+ excludeGroupIds
+ java.lang.String
+ 1.1.0
+ false
+ true
+ Comma separated list of groupId names to exclude (exact match).
+
+
+ excludes
+ java.util.List
+ 1.1.0
+ false
+ true
+ Collection of artifact definitions to exclude. The {@link Exclude} element defines
+a {@code groupId} and {@code artifactId} mandatory properties and an optional
+{@code classifier} property.
+
+
+ folders
+ java.lang.String[]
+ 1.0.0
+ false
+ true
+ Additional folders besides the classes directory that should be added to the
+classpath.
+
+
+ fork
+ boolean
+ 1.2.0
+ false
+ true
+ Flag to indicate if the run processes should be forked. Disabling forking will
+disable some features such as an agent, custom JVM arguments, devtools or
+specifying the working directory to use.
+
+
+ includes
+ java.util.List
+ 1.2.0
+ false
+ true
+ Collection of artifact definitions to include. The {@link Include} element defines
+a {@code groupId} and {@code artifactId} mandatory properties and an optional
+{@code classifier} property.
+
+
+ jmxName
+ java.lang.String
+ false
+ true
+ The JMX name of the automatically deployed MBean managing the lifecycle of the
+spring application.
+
+
+ jmxPort
+ int
+ false
+ true
+ The port to use to expose the platform MBeanServer if the application is forked.
+
+
+ jvmArguments
+ java.lang.String
+ 1.1.0
+ false
+ true
+ JVM arguments that should be associated with the forked process used to run the
+application. On command line, make sure to wrap multiple values between quotes.
+NOTE: a forked process is required to use this feature.
+
+
+ mainClass
+ java.lang.String
+ 1.0.0
+ false
+ true
+ The name of the main class. If not specified the first compiled class found that
+contains a 'main' method will be used.
+
+
+ maxAttempts
+ int
+ false
+ true
+ The maximum number of attempts to check if the spring application is ready.
+Combined with the "wait" argument, this gives a global timeout value (30 sec by
+default)
+
+
+ noverify
+ boolean
+ 1.0.0
+ false
+ true
+ Flag to say that the agent requires -noverify.
+
+
+ profiles
+ java.lang.String[]
+ 1.3.0
+ false
+ true
+ The spring profiles to activate. Convenience shortcut of specifying the
+'spring.profiles.active' argument. On command line use commas to separate multiple
+profiles.
+
+
+ project
+ org.apache.maven.project.MavenProject
+ 1.0.0
+ true
+ false
+ The Maven project.
+
+
+ skip
+ boolean
+ 1.3.2
+ false
+ true
+ Skip the execution.
+
+
+ systemPropertyVariables
+ java.util.Map
+ 2.1.0
+ false
+ true
+ List of JVM system properties to pass to the process. NOTE: a forked process is
+required to use this feature.
+
+
+ useTestClasspath
+ java.lang.Boolean
+ 1.3.0
+ false
+ true
+ Flag to include the test classpath when running.
+
+
+ wait
+ long
+ false
+ true
+ The number of milli-seconds to wait between each attempt to check if the spring
+application is ready.
+
+
+ workingDirectory
+ java.io.File
+ 1.5.0
+ false
+ true
+ Current working directory to use for the application. If not specified, basedir
+will be used. NOTE: a forked process is required to use this feature.
+
+
+
+ ${spring-boot.run.addResources}
+ ${spring-boot.run.agent}
+ ${spring-boot.run.agents}
+ ${spring-boot.run.arguments}
+
+ ${spring-boot.excludeGroupIds}
+ ${spring-boot.excludes}
+ ${spring-boot.run.folders}
+ ${spring-boot.run.fork}
+ ${spring-boot.includes}
+ ${spring-boot.run.jvmArguments}
+ ${spring-boot.run.main-class}
+ ${spring-boot.run.noverify}
+ ${spring-boot.run.profiles}
+
+ ${spring-boot.run.skip}
+ ${spring-boot.run.useTestClasspath}
+ ${spring-boot.run.workingDirectory}
+
+
+
+ stop
+ Stop a spring application that has been started by the "start" goal. Typically invoked
+once a test suite has completed.
+ false
+ true
+ false
+ false
+ false
+ true
+ post-integration-test
+ org.springframework.boot.maven.StopMojo
+ java
+ per-lookup
+ once-per-session
+ 1.3.0
+ false
+
+
+ fork
+ java.lang.Boolean
+ 1.3.0
+ false
+ true
+ Flag to indicate if process to stop was forked. By default, the value is inherited
+from the {@link MavenProject}. If it is set, it must match the value used to
+{@link StartMojo start} the process.
+
+
+ jmxName
+ java.lang.String
+ false
+ true
+ The JMX name of the automatically deployed MBean managing the lifecycle of the
+application.
+
+
+ jmxPort
+ int
+ false
+ true
+ The port to use to lookup the platform MBeanServer if the application has been
+forked.
+
+
+ project
+ org.apache.maven.project.MavenProject
+ 1.4.1
+ true
+ false
+ The Maven project.
+
+
+ skip
+ boolean
+ 1.3.2
+ false
+ true
+ Skip the execution.
+
+
+
+ ${spring-boot.stop.fork}
+
+ ${spring-boot.stop.skip}
+
+
+
+
+
diff --git a/ci/pipeline.yml b/ci/pipeline.yml
index 73131464cab7..e2aeb3871f2f 100644
--- a/ci/pipeline.yml
+++ b/ci/pipeline.yml
@@ -3,7 +3,7 @@ resource_types:
type: docker-image
source:
repository: springio/artifactory-resource
- tag: 0.0.10
+ tag: 0.0.11-SNAPSHOT
- name: pull-request
type: docker-image
source:
@@ -209,19 +209,9 @@ jobs:
timeout: ((task-timeout))
image: spring-boot-ci-image
file: git-repo/ci/tasks/build-project.yml
- - in_parallel:
- - task: build-smoke-tests
- timeout: ((task-timeout))
- image: spring-boot-ci-image
- file: git-repo/ci/tasks/build-smoke-tests.yml
- - task: build-integration-tests
- timeout: ((task-timeout))
- image: spring-boot-ci-image
- file: git-repo/ci/tasks/build-integration-tests.yml
- - task: build-deployment-tests
- timeout: ((task-timeout))
- image: spring-boot-ci-image
- file: git-repo/ci/tasks/build-deployment-tests.yml
+ params:
+ GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
+ GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
on_failure:
do:
- put: repo-status-build
@@ -241,16 +231,15 @@ jobs:
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
disable_checksum_uploads: true
- exclude:
- - "**/*.effective-pom"
- - "**/spring-boot-configuration-docs/**"
- - "**/spring-boot-test-support/**"
+ threads: 8
artifact_set:
- include:
- "/**/spring-boot-docs-*.zip"
properties:
"zip.type": "docs"
"zip.deployed": "false"
+ get_params:
+ threads: 8
on_failure:
do:
- put: slack-alert
@@ -283,19 +272,6 @@ jobs:
timeout: ((task-timeout))
image: spring-boot-ci-image
file: git-repo/ci/tasks/build-pr-project.yml
- - in_parallel:
- - task: build-smoke-tests
- timeout: ((task-timeout))
- image: spring-boot-ci-image
- file: git-repo/ci/tasks/build-smoke-tests.yml
- - task: build-integration-tests
- timeout: ((task-timeout))
- image: spring-boot-ci-image
- file: git-repo/ci/tasks/build-integration-tests.yml
- - task: build-deployment-tests
- timeout: ((task-timeout))
- image: spring-boot-ci-image
- file: git-repo/ci/tasks/build-deployment-tests.yml
on_success:
put: git-pull-request
params:
@@ -321,19 +297,9 @@ jobs:
timeout: ((task-timeout))
image: spring-boot-jdk11-ci-image
file: git-repo/ci/tasks/build-project.yml
- - in_parallel:
- - task: build-smoke-tests
- timeout: ((task-timeout))
- image: spring-boot-jdk11-ci-image
- file: git-repo/ci/tasks/build-smoke-tests.yml
- - task: build-integration-tests
- timeout: ((task-timeout))
- image: spring-boot-jdk11-ci-image
- file: git-repo/ci/tasks/build-integration-tests.yml
- - task: build-deployment-tests
- timeout: ((task-timeout))
- image: spring-boot-jdk11-ci-image
- file: git-repo/ci/tasks/build-deployment-tests.yml
+ params:
+ GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
+ GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
on_failure:
do:
- put: repo-status-jdk11-build
@@ -362,24 +328,14 @@ jobs:
- put: repo-status-jdk13-build
params: { state: "pending", commit: "git-repo" }
- do:
- - task: build-project
- privileged: true
- timeout: ((task-timeout))
- image: spring-boot-jdk13-ci-image
- file: git-repo/ci/tasks/build-project.yml
- - in_parallel:
- - task: build-smoke-tests
- timeout: ((task-timeout))
- image: spring-boot-jdk13-ci-image
- file: git-repo/ci/tasks/build-smoke-tests.yml
- - task: build-integration-tests
- timeout: ((task-timeout))
- image: spring-boot-jdk13-ci-image
- file: git-repo/ci/tasks/build-integration-tests.yml
- - task: build-deployment-tests
- timeout: ((task-timeout))
- image: spring-boot-jdk13-ci-image
- file: git-repo/ci/tasks/build-deployment-tests.yml
+ - task: build-project
+ privileged: true
+ timeout: ((task-timeout))
+ image: spring-boot-jdk13-ci-image
+ file: git-repo/ci/tasks/build-project.yml
+ params:
+ GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
+ GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
on_failure:
do:
- put: repo-status-jdk13-build
@@ -412,6 +368,9 @@ jobs:
tags:
- WIN64
timeout: ((task-timeout))
+ params:
+ GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
+ GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
on_failure:
do:
- put: slack-alert
@@ -437,6 +396,8 @@ jobs:
file: git-repo/ci/tasks/stage.yml
params:
RELEASE_TYPE: M
+ GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
+ GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
- put: artifactory-repo
params:
<<: *artifactory-params
@@ -455,6 +416,8 @@ jobs:
file: git-repo/ci/tasks/stage.yml
params:
RELEASE_TYPE: RC
+ GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
+ GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
- put: artifactory-repo
params:
<<: *artifactory-params
@@ -473,6 +436,8 @@ jobs:
file: git-repo/ci/tasks/stage.yml
params:
RELEASE_TYPE: RELEASE
+ GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
+ GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
- put: artifactory-repo
params:
<<: *artifactory-params
diff --git a/ci/scripts/build-deployment-tests.sh b/ci/scripts/build-deployment-tests.sh
deleted file mode 100755
index c93fd0900837..000000000000
--- a/ci/scripts/build-deployment-tests.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-set -e
-
-source $(dirname $0)/common.sh
-repository=$(pwd)/distribution-repository
-
-pushd git-repo > /dev/null
-run_maven -f spring-boot-tests/spring-boot-deployment-tests/pom.xml clean install -U -Dfull -Drepository=file://${repository}
-popd > /dev/null
diff --git a/ci/scripts/build-integration-tests.sh b/ci/scripts/build-integration-tests.sh
deleted file mode 100755
index 953eaebb1c74..000000000000
--- a/ci/scripts/build-integration-tests.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-set -e
-
-source $(dirname $0)/common.sh
-repository=$(pwd)/distribution-repository
-
-pushd git-repo > /dev/null
-run_maven -f spring-boot-tests/spring-boot-integration-tests/pom.xml clean install -U -Dfull -Drepository=file://${repository}
-popd > /dev/null
diff --git a/ci/scripts/build-project-windows.bat b/ci/scripts/build-project-windows.bat
index efade5d1acfa..aa8b62e1abdf 100755
--- a/ci/scripts/build-project-windows.bat
+++ b/ci/scripts/build-project-windows.bat
@@ -1,6 +1,4 @@
SET "JAVA_HOME=C:\opt\jdk-8"
SET PATH=%PATH%;C:\Program Files\Git\usr\bin
cd git-repo
-
-echo ".\mvnw clean install" > build.log
-.\mvnw clean install -U >> build.log 2>&1 || (sleep 1 && tail -n 3000 build.log && exit 1)
\ No newline at end of file
+.\gradlew --no-daemon --max-workers=4 build
diff --git a/ci/scripts/build-project.sh b/ci/scripts/build-project.sh
index da3c44e4723a..767884cbc9b9 100755
--- a/ci/scripts/build-project.sh
+++ b/ci/scripts/build-project.sh
@@ -5,6 +5,5 @@ source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
-run_maven -N clean verify
-run_maven -f spring-boot-project/pom.xml clean deploy -U -Dfull -DaltDeploymentRepository=distribution::default::file://${repository}
+./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
popd > /dev/null
diff --git a/ci/scripts/build-smoke-tests.sh b/ci/scripts/build-smoke-tests.sh
deleted file mode 100755
index cfb839c0efa7..000000000000
--- a/ci/scripts/build-smoke-tests.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-set -e
-
-source $(dirname $0)/common.sh
-repository=$(pwd)/distribution-repository
-
-pushd git-repo > /dev/null
-run_maven -f spring-boot-tests/spring-boot-smoke-tests/pom.xml clean install -U -Dfull -Drepository=file://${repository}
-popd > /dev/null
diff --git a/ci/scripts/stage.sh b/ci/scripts/stage.sh
index 2ff3a71bb375..79aa2d584610 100755
--- a/ci/scripts/stage.sh
+++ b/ci/scripts/stage.sh
@@ -12,7 +12,7 @@ git clone git-repo stage-git-repo > /dev/null
pushd stage-git-repo > /dev/null
-snapshotVersion=$( get_revision_from_pom )
+snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
if [[ $RELEASE_TYPE = "M" ]]; then
stageVersion=$( get_next_milestone_release $snapshotVersion)
nextVersion=$snapshotVersion
@@ -27,23 +27,20 @@ else
fi
echo "Staging $stageVersion (next version will be $nextVersion)"
+sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
-set_revision_to_pom "$stageVersion"
git config user.name "Spring Buildmaster" > /dev/null
git config user.email "buildmaster@springframework.org" > /dev/null
-git add pom.xml > /dev/null
+git add gradle.properties > /dev/null
git commit -m"Release v$stageVersion" > /dev/null
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
-run_maven -f spring-boot-project/pom.xml clean deploy -U -Dfull -DaltDeploymentRepository=distribution::default::file://${repository}
-run_maven -f spring-boot-tests/spring-boot-smoke-tests/pom.xml clean install -U -Dfull -Drepository=file://${repository}
-run_maven -f spring-boot-tests/spring-boot-integration-tests/pom.xml clean install -U -Dfull -Drepository=file://${repository}
-run_maven -f spring-boot-tests/spring-boot-deployment-tests/pom.xml clean install -U -Dfull -Drepository=file://${repository}
+./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
git reset --hard HEAD^ > /dev/null
if [[ $nextVersion != $snapshotVersion ]]; then
echo "Setting next development version (v$nextVersion)"
- set_revision_to_pom "$nextVersion"
+ sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
git add pom.xml > /dev/null
git commit -m"Next development version (v$nextVersion)" > /dev/null
fi;
diff --git a/ci/tasks/build-deployment-tests.yml b/ci/tasks/build-deployment-tests.yml
deleted file mode 100644
index e45a4c2a301b..000000000000
--- a/ci/tasks/build-deployment-tests.yml
+++ /dev/null
@@ -1,10 +0,0 @@
----
-platform: linux
-inputs:
-- name: git-repo
-- name: distribution-repository
-caches:
-- path: maven
-- path: gradle
-run:
- path: git-repo/ci/scripts/build-deployment-tests.sh
diff --git a/ci/tasks/build-integration-tests.yml b/ci/tasks/build-integration-tests.yml
deleted file mode 100644
index bc96aa6cef45..000000000000
--- a/ci/tasks/build-integration-tests.yml
+++ /dev/null
@@ -1,10 +0,0 @@
----
-platform: linux
-inputs:
-- name: git-repo
-- name: distribution-repository
-caches:
-- path: maven
-- path: gradle
-run:
- path: git-repo/ci/scripts/build-integration-tests.sh
diff --git a/ci/tasks/build-pr-project.yml b/ci/tasks/build-pr-project.yml
index b4576a4849fc..c3dbfc8c05ae 100644
--- a/ci/tasks/build-pr-project.yml
+++ b/ci/tasks/build-pr-project.yml
@@ -7,5 +7,8 @@ outputs:
caches:
- path: maven
- path: gradle
+params:
+ CI: true
+ GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: git-repo/ci/scripts/build-project.sh
diff --git a/ci/tasks/build-project.yml b/ci/tasks/build-project.yml
index 50e222c7d247..a22b6f5d9dea 100644
--- a/ci/tasks/build-project.yml
+++ b/ci/tasks/build-project.yml
@@ -8,6 +8,11 @@ caches:
- path: maven
- path: gradle
- path: embedmongo
+params:
+ CI: true
+ GRADLE_ENTERPRISE_CACHE_USERNAME:
+ GRADLE_ENTERPRISE_CACHE_PASSWORD:
+ GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
@@ -16,4 +21,3 @@ run:
source /docker-lib.sh
start_docker
${PWD}/git-repo/ci/scripts/build-project.sh
-
diff --git a/ci/tasks/build-smoke-tests.yml b/ci/tasks/build-smoke-tests.yml
deleted file mode 100644
index a38dfb2ef9ff..000000000000
--- a/ci/tasks/build-smoke-tests.yml
+++ /dev/null
@@ -1,10 +0,0 @@
----
-platform: linux
-inputs:
-- name: git-repo
-- name: distribution-repository
-caches:
-- path: maven
-- path: gradle
-run:
- path: git-repo/ci/scripts/build-smoke-tests.sh
diff --git a/ci/tasks/stage.yml b/ci/tasks/stage.yml
index f486313ae918..dd21db74f8e9 100644
--- a/ci/tasks/stage.yml
+++ b/ci/tasks/stage.yml
@@ -7,6 +7,10 @@ outputs:
- name: distribution-repository
params:
RELEASE_TYPE:
+ CI: true
+ GRADLE_ENTERPRISE_CACHE_USERNAME:
+ GRADLE_ENTERPRISE_CACHE_PASSWORD:
+ GRADLE_ENTERPRISE_URL: https://ge.spring.io
caches:
- path: maven
- path: gradle
diff --git a/eclipse/spring-boot-project.setup b/eclipse/spring-boot-project.setup
index 79100f5284de..2048d3b1bd05 100644
--- a/eclipse/spring-boot-project.setup
+++ b/eclipse/spring-boot-project.setup
@@ -4,13 +4,12 @@
xmlns:xmi="http://www.omg.org/XMI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdt="http://www.eclipse.org/oomph/setup/jdt/1.0"
- xmlns:maven="http://www.eclipse.org/oomph/setup/maven/1.0"
xmlns:predicates="http://www.eclipse.org/oomph/predicates/1.0"
xmlns:setup="http://www.eclipse.org/oomph/setup/1.0"
xmlns:setup.p2="http://www.eclipse.org/oomph/setup/p2/1.0"
xmlns:setup.workingsets="http://www.eclipse.org/oomph/setup/workingsets/1.0"
xmlns:workingsets="http://www.eclipse.org/oomph/workingsets/1.0"
- xsi:schemaLocation="http://www.eclipse.org/oomph/setup/jdt/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/JDT.ecore http://www.eclipse.org/oomph/setup/maven/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/Maven.ecore http://www.eclipse.org/oomph/predicates/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/Predicates.ecore http://www.eclipse.org/oomph/setup/workingsets/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/SetupWorkingSets.ecore http://www.eclipse.org/oomph/workingsets/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/WorkingSets.ecore"
+ xsi:schemaLocation="http://www.eclipse.org/oomph/setup/jdt/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/JDT.ecore http://www.eclipse.org/oomph/predicates/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/Predicates.ecore http://www.eclipse.org/oomph/setup/workingsets/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/SetupWorkingSets.ecore http://www.eclipse.org/oomph/workingsets/1.0 https://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/WorkingSets.ecore"
name="spring.boot.2.3.x"
label="Spring Boot 2.3.x">
-
-
-
- spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin
-
-
-
search) {
+ def query = search.collect { name, value ->
+ "search.names=${encodeURL(name)}&search.values=${encodeURL(value)}"
+ }.join('&')
+
+ "$gradleEnterprise.buildScan.server/scans?$query"
+}
+
+String encodeURL(String url) {
+ URLEncoder.encode(url, 'UTF-8')
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000000..cc4fdc293d0e
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
similarity index 91%
rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradle/wrapper/gradle-wrapper.properties
rename to gradle/wrapper/gradle-wrapper.properties
index 290541c73864..94920145f34e 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradlew b/gradlew
similarity index 75%
rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradlew
rename to gradlew
index cccdd3d517fc..2fe81a7d95e4 100755
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradlew
+++ b/gradlew
@@ -1,5 +1,21 @@
#!/usr/bin/env sh
+#
+# Copyright 2015 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.
+#
+
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -109,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
@@ -138,19 +154,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +175,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
exec "$JAVACMD" "$@"
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradlew.bat b/gradlew.bat
similarity index 75%
rename from spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradlew.bat
rename to gradlew.bat
index f9553162f122..9618d8d9607c 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/gradlew.bat
+++ b/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index dd169ece2b29..000000000000
--- a/pom.xml
+++ /dev/null
@@ -1,384 +0,0 @@
-
-
- 4.0.0
- org.springframework.boot
- spring-boot-build
- ${revision}
- pom
- Spring Boot Build
- Spring Boot Build
-
- 2.3.0.BUILD-SNAPSHOT
- ${basedir}
-
-
-
-
-
- default
-
-
- !disable-spring-boot-default-profile
-
-
-
- 0.0.17
- 0.0.4.RELEASE
-
-
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
- 3.0.0
-
-
- com.puppycrawl.tools
- checkstyle
- 8.22
-
-
- io.spring.javaformat
- spring-javaformat-checkstyle
- ${spring-javaformat.version}
-
-
- io.spring.nohttp
- nohttp-checkstyle
- ${nohttp-checkstyle.version}
-
-
-
-
- checkstyle-validation
- validate
-
- ${disable.checks}
- src/checkstyle/checkstyle.xml
- src/checkstyle/checkstyle-suppressions.xml
- true
- main.basedir=${main.basedir}
- UTF-8
-
-
- check
-
-
-
- nohttp-checkstyle-validation
- validate
-
- ${disable.checks}
- src/checkstyle/nohttp-checkstyle.xml
- src/checkstyle/nohttp-checkstyle-suppressions.xml
- main.basedir=${main.basedir}
- UTF-8
- ${basedir}
- **/*
- **/.git/**/*,**/target/**/,**/.flattened-pom.xml,**/*.class,**/spring-boot-gradle-plugin/build/**,**/spring-boot-gradle-plugin/bin/**
-
-
- check
-
- false
-
-
-
-
- io.spring.javaformat
- spring-javaformat-maven-plugin
- ${spring-javaformat.version}
-
-
- validate
-
- ${disable.checks}
-
-
- validate
-
-
-
-
-
-
-
- spring-boot-project
-
- spring-boot-tests
-
-
-
-
- m2e
-
-
- m2e.version
-
-
-
- spring-boot-project
- spring-boot-tests
-
-
-
- repository
-
-
- repository
-
-
-
-
- repository
- ${repository}
-
- true
-
-
-
-
-
- repository
- ${repository}
-
- true
-
-
-
-
-
-
-
-
- central
- https://repo.maven.apache.org/maven2
-
- false
-
-
-
- spring-milestone
- Spring Milestone
- https://repo.spring.io/milestone
-
- false
-
-
-
- spring-snapshot
- Spring Snapshot
- https://repo.spring.io/snapshot
-
- true
-
-
-
- rabbit-milestone
- Rabbit Milestone
- https://dl.bintray.com/rabbitmq/maven-milestones
-
- false
-
-
-
-
-
- central
- https://repo.maven.apache.org/maven2
-
- false
-
-
-
- spring-release
- Spring Release
- https://repo.spring.io/release
-
-
- spring-milestone
- Spring Milestone
- https://repo.spring.io/milestone
-
- false
-
-
-
- spring-snapshot
- Spring Snapshot
- https://repo.spring.io/snapshot
-
- true
-
-
-
-
-
-
-
-
- org.eclipse.m2e
- lifecycle-mapping
- 1.0.0
-
-
-
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
- [1,)
-
- check
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-dependency-plugin
- [1,)
-
- copy
- copy-dependencies
- unpack
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- [1,)
-
- enforce
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-invoker-plugin
- [1,)
-
- install
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-plugin-plugin
- [1,)
-
- descriptor
- helpmojo
-
-
-
-
-
-
-
-
- org.basepom.maven
- duplicate-finder-maven-plugin
- [1,)
-
- check
-
-
-
-
-
-
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
- [1,)
-
- reserve-network-port
-
-
-
-
-
-
-
-
- org.codehaus.mojo
- flatten-maven-plugin
- [1,)
-
- flatten
-
-
-
-
-
-
-
-
- org.jetbrains.kotlin
- kotlin-maven-plugin
- [1,)
-
- compile
- test-compile
-
-
-
-
-
-
-
-
- org.jooq
- jooq-codegen-maven
- [1,)
-
- generate
-
-
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
- [1,)
-
- build-info
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 000000000000..43d7fd1cfbae
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,71 @@
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ maven { url 'https://repo.spring.io/snapshot' }
+ }
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == 'org.jetbrains.kotlin.jvm') {
+ useVersion "${kotlinVersion}"
+ }
+ if (requested.id.id == 'org.jetbrains.kotlin.plugin.spring') {
+ useVersion "${kotlinVersion}"
+ }
+ }
+ }
+}
+
+plugins {
+ id 'com.gradle.enterprise' version '3.1'
+}
+
+apply from: "$rootDir/gradle/build-scan-user-data.gradle"
+gradleEnterprise {
+ buildScan {
+ captureTaskInputFiles = true
+ obfuscation {
+ ipAddresses { addresses -> addresses.collect { address -> '0.0.0.0'} }
+ }
+ publishAlwaysIf(System.getenv('GRADLE_ENTERPRISE_URL') != null)
+ server = 'https://ge.spring.io'
+ }
+}
+
+apply from: new File(settingsDir, 'gradle/build-cache-settings.gradle')
+
+rootProject.name='spring-boot-build'
+
+include 'spring-boot-project:spring-boot-dependencies'
+include 'spring-boot-project:spring-boot-parent'
+include 'spring-boot-project:spring-boot-tools:spring-boot-antlib'
+include 'spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-processor'
+include 'spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata'
+include 'spring-boot-project:spring-boot-tools:spring-boot-configuration-processor'
+include 'spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin'
+include 'spring-boot-project:spring-boot-tools:spring-boot-loader'
+include 'spring-boot-project:spring-boot-tools:spring-boot-loader-tools'
+include 'spring-boot-project:spring-boot-tools:spring-boot-maven-plugin'
+include 'spring-boot-project:spring-boot-tools:spring-boot-test-support'
+include 'spring-boot-project:spring-boot'
+include 'spring-boot-project:spring-boot-autoconfigure'
+include 'spring-boot-project:spring-boot-actuator'
+include 'spring-boot-project:spring-boot-actuator-autoconfigure'
+include 'spring-boot-project:spring-boot-cli'
+include 'spring-boot-project:spring-boot-devtools'
+include 'spring-boot-project:spring-boot-docs'
+include 'spring-boot-project:spring-boot-properties-migrator'
+include 'spring-boot-project:spring-boot-test'
+include 'spring-boot-project:spring-boot-test-autoconfigure'
+include 'spring-boot-tests:spring-boot-deployment-tests'
+include 'spring-boot-tests:spring-boot-integration-tests:spring-boot-configuration-processor-tests'
+include 'spring-boot-tests:spring-boot-integration-tests:spring-boot-launch-script-tests'
+include 'spring-boot-tests:spring-boot-integration-tests:spring-boot-server-tests'
+
+new File("$rootDir/spring-boot-project/spring-boot-starters").eachFileMatch(groovy.io.FileType.DIRECTORIES, ~/spring-boot-starter.*/) {
+ include "spring-boot-project:spring-boot-starters:$it.name"
+}
+
+new File("$rootDir/spring-boot-tests/spring-boot-smoke-tests").eachFileMatch(groovy.io.FileType.DIRECTORIES, ~/spring-boot-smoke-test.*/) {
+ include "spring-boot-tests:spring-boot-smoke-tests:$it.name"
+}
diff --git a/spring-boot-project/pom.xml b/spring-boot-project/pom.xml
deleted file mode 100644
index 2e893ff2819b..000000000000
--- a/spring-boot-project/pom.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-build
- ${revision}
-
- spring-boot-project
- pom
- Spring Boot Project
- Spring Boot Project
-
- ${basedir}/..
-
-
- spring-boot-dependencies
- spring-boot-parent
- spring-boot
- spring-boot-actuator
- spring-boot-actuator-autoconfigure
- spring-boot-autoconfigure
- spring-boot-devtools
- spring-boot-properties-migrator
- spring-boot-test
- spring-boot-test-autoconfigure
- spring-boot-tools
- spring-boot-starters
- spring-boot-cli
- spring-boot-docs
-
-
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle
new file mode 100644
index 000000000000..5c7076e9ef42
--- /dev/null
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle
@@ -0,0 +1,185 @@
+plugins {
+ id 'java-library'
+ id 'org.asciidoctor.jvm.convert'
+ id 'org.asciidoctor.jvm.pdf'
+ id 'org.springframework.boot.auto-configuration'
+ id 'org.springframework.boot.conventions'
+ id 'org.springframework.boot.deployed'
+ id 'org.springframework.boot.optional-dependencies'
+}
+
+description = 'Spring Boot Actuator AutoConfigure'
+
+configurations {
+ asciidoctorExtensions
+ documentation
+}
+
+dependencies {
+ asciidoctorExtensions enforcedPlatform(project(':spring-boot-project:spring-boot-dependencies'))
+ asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
+
+ api project(':spring-boot-project:spring-boot-actuator')
+
+ implementation enforcedPlatform(project(':spring-boot-project:spring-boot-dependencies'))
+ implementation project(':spring-boot-project:spring-boot')
+ implementation project(':spring-boot-project:spring-boot-autoconfigure')
+ implementation 'com.fasterxml.jackson.core:jackson-databind'
+ implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
+ implementation 'org.springframework:spring-core'
+ implementation 'org.springframework:spring-context'
+
+ optional enforcedPlatform(project(':spring-boot-project:spring-boot-dependencies'))
+ optional 'ch.qos.logback:logback-classic'
+ optional 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
+ optional 'com.github.ben-manes.caffeine:caffeine'
+ optional 'com.hazelcast:hazelcast'
+ optional 'com.hazelcast:hazelcast-spring'
+ optional 'com.sun.mail:jakarta.mail'
+ optional 'com.zaxxer:HikariCP'
+ optional 'io.dropwizard.metrics:metrics-jmx'
+ optional 'io.lettuce:lettuce-core'
+ optional 'io.micrometer:micrometer-core'
+ optional 'io.micrometer:micrometer-jersey2'
+ optional 'io.micrometer:micrometer-registry-appoptics'
+ optional 'io.micrometer:micrometer-registry-atlas'
+ optional 'io.micrometer:micrometer-registry-datadog'
+ optional 'io.micrometer:micrometer-registry-dynatrace'
+ optional 'io.micrometer:micrometer-registry-elastic'
+ optional 'io.micrometer:micrometer-registry-ganglia'
+ optional 'io.micrometer:micrometer-registry-graphite'
+ optional 'io.micrometer:micrometer-registry-humio'
+ optional 'io.micrometer:micrometer-registry-influx'
+ optional 'io.micrometer:micrometer-registry-jmx'
+ optional 'io.micrometer:micrometer-registry-kairos'
+ optional 'io.micrometer:micrometer-registry-new-relic'
+ optional 'io.micrometer:micrometer-registry-prometheus'
+ optional 'io.micrometer:micrometer-registry-stackdriver'
+ optional 'io.prometheus:simpleclient_pushgateway'
+ optional 'io.micrometer:micrometer-registry-signalfx'
+ optional 'io.micrometer:micrometer-registry-statsd'
+ optional 'io.micrometer:micrometer-registry-wavefront'
+ optional 'io.projectreactor.netty:reactor-netty'
+ optional 'io.searchbox:jest'
+ optional 'jakarta.jms:jakarta.jms-api'
+ optional 'jakarta.servlet:jakarta.servlet-api'
+ optional 'javax.cache:cache-api'
+ optional 'net.sf.ehcache:ehcache'
+ optional 'org.apache.activemq:activemq-broker'
+ optional 'org.apache.commons:commons-dbcp2'
+ optional 'org.apache.kafka:kafka-clients'
+ optional 'org.apache.tomcat.embed:tomcat-embed-core'
+ optional 'org.apache.tomcat.embed:tomcat-embed-el'
+ optional 'org.apache.tomcat:tomcat-jdbc'
+ optional 'org.aspectj:aspectjweaver'
+ optional 'org.eclipse.jetty:jetty-server'
+ optional 'org.elasticsearch:elasticsearch'
+ optional 'org.elasticsearch.client:elasticsearch-rest-client'
+ optional 'org.flywaydb:flyway-core'
+ optional 'org.glassfish.jersey.core:jersey-server'
+ optional 'org.glassfish.jersey.containers:jersey-container-servlet-core'
+ optional 'org.hibernate:hibernate-core'
+ optional 'org.hibernate.validator:hibernate-validator'
+ optional 'org.influxdb:influxdb-java'
+ optional 'org.jolokia:jolokia-core'
+ optional 'org.infinispan:infinispan-spring4-embedded'
+ optional 'org.liquibase:liquibase-core'
+ optional 'org.mongodb:mongodb-driver-async'
+ optional 'org.mongodb:mongodb-driver-reactivestreams'
+ optional 'org.springframework:spring-jdbc'
+ optional 'org.springframework:spring-jms'
+ optional 'org.springframework:spring-messaging'
+ optional 'org.springframework:spring-webflux'
+ optional 'org.springframework:spring-webmvc'
+ optional 'org.springframework.amqp:spring-rabbit'
+ optional 'org.springframework.data:spring-data-cassandra'
+ optional 'org.springframework.data:spring-data-couchbase'
+ optional 'org.springframework.data:spring-data-ldap'
+ optional 'org.springframework.data:spring-data-mongodb'
+ optional 'org.springframework.data:spring-data-neo4j'
+ optional 'org.springframework.data:spring-data-redis'
+ optional 'org.springframework.data:spring-data-solr'
+ optional 'org.springframework.integration:spring-integration-core'
+ optional 'org.springframework.security:spring-security-config'
+ optional 'org.springframework.security:spring-security-web'
+ optional 'org.springframework.session:spring-session-core'
+ optional 'redis.clients:jedis'
+
+ testImplementation project(':spring-boot-project:spring-boot-test')
+ testImplementation project(':spring-boot-project:spring-boot-tools:spring-boot-test-support')
+ testImplementation 'io.projectreactor:reactor-test'
+ testImplementation 'com.squareup.okhttp3:mockwebserver'
+ testImplementation 'com.jayway.jsonpath:json-path'
+ testImplementation 'io.undertow:undertow-core'
+ testImplementation ('io.undertow:undertow-servlet') {
+ exclude group: 'org.jboss.spec.javax.annotation', module: 'jboss-annotations-api_1.2_spec'
+ exclude group: 'org.jboss.spec.javax.servlet', module: 'jboss-servlet-api_4.0_spec'
+ }
+ testImplementation 'javax.xml.bind:jaxb-api'
+ testImplementation 'org.apache.logging.log4j:log4j-to-slf4j'
+ testImplementation 'org.aspectj:aspectjrt'
+ testImplementation 'org.assertj:assertj-core'
+ testImplementation 'org.eclipse.jetty:jetty-webapp'
+ testImplementation 'org.glassfish.jersey.ext:jersey-spring4'
+ testImplementation 'org.glassfish.jersey.media:jersey-media-json-jackson'
+ testImplementation 'org.hamcrest:hamcrest'
+ testImplementation 'org.hsqldb:hsqldb'
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+ testImplementation 'org.mockito:mockito-core'
+ testImplementation 'org.skyscreamer:jsonassert'
+ testImplementation 'org.springframework:spring-orm'
+ testImplementation 'org.springframework.data:spring-data-elasticsearch'
+ testImplementation 'org.springframework.data:spring-data-rest-webmvc'
+ testImplementation 'org.springframework.integration:spring-integration-jmx'
+ testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
+ testImplementation 'org.springframework.restdocs:spring-restdocs-webtestclient'
+ testImplementation 'org.springframework.security:spring-security-test'
+ testImplementation 'org.yaml:snakeyaml'
+
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+ testRuntimeOnly 'org.springframework.security:spring-security-oauth2-jose'
+ testRuntimeOnly 'org.springframework.security:spring-security-oauth2-resource-server'
+}
+
+compileJava {
+ options.compilerArgs << '-parameters'
+}
+
+compileTestJava {
+ options.compilerArgs << '-parameters'
+}
+
+test {
+ outputs.dir("$buildDir/generated-snippets")
+}
+
+asciidoctor {
+ configurations 'asciidoctorExtensions'
+ dependsOn test
+ sources {
+ include 'index.adoc'
+ }
+}
+
+asciidoctorPdf {
+ configurations 'asciidoctorExtensions'
+ dependsOn test
+ sources {
+ include 'index.adoc'
+ }
+}
+
+task zip(type: Zip) {
+ dependsOn asciidoctor, asciidoctorPdf
+ duplicatesStrategy 'fail'
+ from(asciidoctorPdf.outputDir) {
+ into 'pdf'
+ }
+ from(asciidoctor.outputDir) {
+ into 'html'
+ }
+}
+
+artifacts {
+ documentation zip
+}
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml
deleted file mode 100644
index aa824b2ae100..000000000000
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml
+++ /dev/null
@@ -1,863 +0,0 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-parent
- ${revision}
- ../spring-boot-parent
-
- spring-boot-actuator-autoconfigure
- Spring Boot Actuator AutoConfigure
- Spring Boot Actuator AutoConfigure
-
- ${basedir}/../..
- ${project.build.directory}/refdocs/
-
-
- ${git.url}
- ${git.connection}
- ${git.developerConnection}
-
-
-
-
- org.springframework.boot
- spring-boot-actuator
-
-
- org.springframework.boot
- spring-boot-autoconfigure
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
- org.springframework
- spring-core
-
-
- org.springframework
- spring-context
-
-
-
- ch.qos.logback
- logback-classic
- true
-
-
- com.fasterxml.jackson.dataformat
- jackson-dataformat-xml
- true
-
-
- com.github.ben-manes.caffeine
- caffeine
- true
-
-
- com.hazelcast
- hazelcast
- true
-
-
- com.hazelcast
- hazelcast-spring
- true
-
-
- com.sun.mail
- jakarta.mail
- true
-
-
- com.zaxxer
- HikariCP
- true
-
-
- io.dropwizard.metrics
- metrics-jmx
- true
-
-
- io.lettuce
- lettuce-core
- true
-
-
- io.micrometer
- micrometer-core
- true
-
-
- io.micrometer
- micrometer-jersey2
- true
-
-
- io.micrometer
- micrometer-registry-appoptics
- true
-
-
- io.micrometer
- micrometer-registry-atlas
- true
-
-
- io.micrometer
- micrometer-registry-datadog
- true
-
-
- io.micrometer
- micrometer-registry-dynatrace
- true
-
-
- io.micrometer
- micrometer-registry-elastic
- true
-
-
- io.micrometer
- micrometer-registry-ganglia
- true
-
-
- io.micrometer
- micrometer-registry-graphite
- true
-
-
- io.micrometer
- micrometer-registry-humio
- true
-
-
- io.micrometer
- micrometer-registry-influx
- true
-
-
- io.micrometer
- micrometer-registry-jmx
- true
-
-
- io.micrometer
- micrometer-registry-kairos
- true
-
-
- io.micrometer
- micrometer-registry-new-relic
- true
-
-
- io.micrometer
- micrometer-registry-prometheus
- true
-
-
- io.prometheus
- simpleclient_pushgateway
- true
-
-
- io.micrometer
- micrometer-registry-signalfx
- true
-
-
- io.micrometer
- micrometer-registry-stackdriver
- true
-
-
- io.micrometer
- micrometer-registry-statsd
- true
-
-
- io.micrometer
- micrometer-registry-wavefront
- true
-
-
- io.projectreactor.netty
- reactor-netty
- true
-
-
- io.searchbox
- jest
- true
-
-
- jakarta.jms
- jakarta.jms-api
- true
-
-
- jakarta.servlet
- jakarta.servlet-api
- true
-
-
- jakarta.persistence
- jakarta.persistence-api
- true
-
-
- jakarta.ws.rs
- jakarta.ws.rs-api
- true
-
-
- javax.cache
- cache-api
- true
-
-
- net.sf.ehcache
- ehcache
- true
-
-
- org.apache.activemq
- activemq-broker
- true
-
-
- geronimo-jms_1.1_spec
- org.apache.geronimo.specs
-
-
-
-
- org.apache.commons
- commons-dbcp2
- true
-
-
- org.apache.kafka
- kafka-clients
- true
-
-
- org.apache.tomcat.embed
- tomcat-embed-core
- true
-
-
- org.apache.tomcat.embed
- tomcat-embed-el
- true
-
-
- org.apache.tomcat
- tomcat-jdbc
- true
-
-
- org.aspectj
- aspectjweaver
- true
-
-
- org.eclipse.jetty
- jetty-server
- true
-
-
- javax.servlet
- javax.servlet-api
-
-
-
-
- org.elasticsearch
- elasticsearch
- true
-
-
- org.elasticsearch.client
- elasticsearch-rest-client
- true
-
-
- org.flywaydb
- flyway-core
- true
-
-
- org.glassfish.jersey.core
- jersey-server
- true
-
-
- javax.validation
- validation-api
-
-
-
-
- org.glassfish.jersey.containers
- jersey-container-servlet-core
- true
-
-
- org.hibernate
- hibernate-core
- true
-
-
- javax.activation
- javax.activation-api
-
-
- javax.xml.bind
- jaxb-api
-
-
- javax.persistence
- javax.persistence-api
-
-
-
-
- org.hibernate.validator
- hibernate-validator
- true
-
-
- javax.validation
- validation-api
-
-
-
-
- org.influxdb
- influxdb-java
- true
-
-
- org.jolokia
- jolokia-core
- true
-
-
- org.infinispan
- infinispan-spring5-embedded
- true
-
-
- org.liquibase
- liquibase-core
- true
-
-
- org.mongodb
- mongodb-driver-async
- true
-
-
- org.mongodb
- mongodb-driver-reactivestreams
- true
-
-
- org.springframework
- spring-jdbc
- true
-
-
- org.springframework
- spring-jms
- true
-
-
- org.springframework
- spring-messaging
- true
-
-
- org.springframework
- spring-webflux
- true
-
-
- org.springframework
- spring-webmvc
- true
-
-
- org.springframework.amqp
- spring-rabbit
- true
-
-
- org.springframework.data
- spring-data-cassandra
- true
-
-
- org.springframework.data
- spring-data-couchbase
- true
-
-
- org.springframework.data
- spring-data-ldap
- true
-
-
- org.springframework.data
- spring-data-mongodb
- true
-
-
- org.springframework.data
- spring-data-neo4j
- true
-
-
- org.springframework.data
- spring-data-redis
- true
-
-
- org.springframework.data
- spring-data-solr
- true
-
-
-
- wstx-asl
- org.codehaus.woodstox
-
-
-
-
- org.springframework.integration
- spring-integration-core
- true
-
-
- org.springframework.security
- spring-security-config
- true
-
-
- org.springframework.security
- spring-security-web
- true
-
-
- org.springframework.session
- spring-session-core
- true
-
-
-
- com.fasterxml.jackson.datatype
- jackson-datatype-jsr310
- runtime
-
-
-
- org.springframework.boot
- spring-boot-autoconfigure-processor
- true
-
-
- org.springframework.boot
- spring-boot-configuration-processor
- true
-
-
-
- org.springframework.boot
- spring-boot-test
- test
-
-
- org.springframework.boot
- spring-boot-test-support
- test
-
-
- io.projectreactor
- reactor-test
- test
-
-
- com.squareup.okhttp3
- mockwebserver
- test
-
-
- org.hamcrest
- hamcrest-core
-
-
-
-
- com.jayway.jsonpath
- json-path
- test
-
-
- io.undertow
- undertow-core
- test
-
-
- io.undertow
- undertow-servlet
- test
-
-
- org.jboss.spec.javax.servlet
- jboss-servlet-api_3.1_spec
-
-
-
-
- jakarta.validation
- jakarta.validation-api
- test
-
-
- jakarta.xml.bind
- jakarta.xml.bind-api
- test
-
-
- org.apache.logging.log4j
- log4j-to-slf4j
- test
-
-
- org.aspectj
- aspectjrt
- test
-
-
- org.eclipse.jetty
- jetty-webapp
- test
-
-
- org.hsqldb
- hsqldb
- test
-
-
- org.glassfish.jersey.ext
- jersey-spring5
- test
-
-
- org.glassfish.jersey.media
- jersey-media-json-jackson
- test
-
-
- org.skyscreamer
- jsonassert
- test
-
-
- org.springframework
- spring-orm
- test
-
-
- org.springframework.data
- spring-data-elasticsearch
- test
-
-
- org.springframework.data
- spring-data-rest-webmvc
- test
-
-
- org.springframework.integration
- spring-integration-jmx
- test
-
-
- org.springframework.restdocs
- spring-restdocs-mockmvc
- test
-
-
- javax.servlet
- javax.servlet-api
-
-
-
-
- org.springframework.restdocs
- spring-restdocs-webtestclient
- test
-
-
- org.springframework.security
- spring-security-test
- test
-
-
- org.springframework.security
- spring-security-oauth2-resource-server
- test
-
-
- org.springframework.security
- spring-security-oauth2-jose
- test
-
-
- org.yaml
- snakeyaml
- test
-
-
- redis.clients
- jedis
- true
-
-
-
-
- full
-
-
- full
-
-
-
-
-
- com.googlecode.maven-download-plugin
- download-maven-plugin
-
-
- unpack-doc-resources
- generate-resources
-
- wget
-
-
- ${spring-doc-resources.url}
- true
- ${refdocs.build.directory}
-
-
-
-
-
- org.apache.maven.plugins
- maven-antrun-plugin
-
-
- ant-contrib
- ant-contrib
- 1.0b3
-
-
- ant
- ant
-
-
-
-
- org.apache.ant
- ant-nodeps
- 1.8.1
-
-
- org.tigris.antelope
- antelopetasks
- 3.2.10
-
-
-
-
- set-up-maven-properties
- prepare-package
-
- run
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- package-docs-zip
- package
-
- run
-
-
-
-
-
-
-