Port the build to Gradle

Closes gh-19609
Closes gh-19608
pull/19618/head
Andy Wilkinson 5 years ago
parent abe95fa8a7
commit ce99db1902

@ -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'

4
.gitignore vendored

@ -22,12 +22,16 @@ MANIFEST.MF
_site/ _site/
activemq-data activemq-data
bin bin
build
!/**/src/**/bin
!/**/src/**/build
build.log build.log
dependency-reduced-pom.xml dependency-reduced-pom.xml
dump.rdb dump.rdb
interpolated*.xml interpolated*.xml
lib/ lib/
manifest.yml manifest.yml
out
overridedb.* overridedb.*
target target
transaction-logs transaction-logs

@ -1 +0,0 @@
-Xmx1536m

Binary file not shown.

@ -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

@ -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 * 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 to apply code formatting conventions. If you use Eclipse and you follow the '`Importing
into eclipse`' instructions below you should get project specific formatting 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] automatically. You can also install the
or format the code from the Maven build by running https://github.com/spring-io/spring-javaformat/#intellij-idea[Spring JavaFormat IntelliJ
`./mvnw io.spring.javaformat:spring-javaformat-maven-plugin:apply`. 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 * 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 * 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 `@author` tag identifying you, and preferably at least a paragraph on what the class is
for. 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 If you don't have an IDE preference we would recommend that you use
https://spring.io/tools/sts[Spring Tools Suite] or https://spring.io/tools/sts[Spring Tools Suite] or
https://eclipse.org[Eclipse] when working with the code. We use the 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 https://projects.eclipse.org/projects/tools.buildship[Buildship] Eclipse plugin for Gradle
should also work without issue. support. Other IDEs and tools should also work without issue.
=== Building from Source === Building from Source
Spring Boot source can be built from the command line using Spring Boot source can be built from the command line using https://gradle.org[Gradle] on
https://maven.apache.org/run-maven/index.html[Apache Maven] on JDK 1.8 or above. We JDK 1.8 or above. We include https://docs.gradle.org/current/userguide/gradle_wrapper.html[Gradle's
include '`Maven Wrapper`' scripts (`./mvnw` or `mvnw.bat`) that you can run rather than wrapper scripts] (`./gradlew` or `gradlew.bat`) that you can run rather than needing to
needing to install Maven locally. install Gradle locally.
The project can be built from the root directory using the standard Gradle command:
==== 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:
[indent=0] [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 === 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 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). `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 ==== 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 all required Eclipse plugins. Projects will be grouped into working-sets to make the code
easier to navigate. 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 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`", `JavaSE-1.8` environment. From preferences select "`Java`", "`Installed JREs`",
"`Execution Environments`" and make sure "`JavaSE-1.8`" points to a Java 1.8 "`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 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 https://projects.eclipse.org/projects/tools.buildship[Buildship] Eclipse plugin. If you
installed it is available from the "`Eclipse marketplace`". 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 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 NOTE: The plugin is optional. Projects can be imported without the plugins, your code
changes just won't be automatically formatted. changes just won't be automatically formatted.
With the requisite eclipse plugins installed you can select With the requisite Eclipse plugins installed you can select
`import existing maven projects` from the `file` menu to import the code. You will `Gradle -> Existing Gradle project` from the `File -> Import…` menu to import the code.
need to import the root `spring-boot` pom and the `spring-boot-smoke-tests` pom separately.
=== Importing into IntelliJ IDEA === Importing into IntelliJ IDEA
**Please, do this first!** Use "`File`" -> "`Open`" and then select the root `build.gradle` file to import the code.
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.
@ -239,16 +186,8 @@ needs to be added.
=== Importing into Other IDEs === 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 == Cloning the git repository on Windows

@ -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'
}
}

@ -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}'
}
----

@ -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()
}

@ -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')

@ -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:
*
* <ul>
* <li>All warnings are made fatal.
* <li>A task is created to resolve and unzip our documentation resources (CSS and
* Javascript).
* <li>For each {@link AsciidoctorTask} (HTML only):
* <ul>
* <li>A task is created to sync the documentation resources to its output directory.
* <li>{@code doctype} {@link AsciidoctorTask#options(Map) option} is configured.
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured for syntax
* highlighting, CSS styling, docinfo, etc.
* </ul>
* <li>For each {@link AbstractAsciidoctorTask} (HTML and PDF):
* <ul>
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured to enable
* warnings for references to missing attributes, the GitHub tag, the Artifactory repo for
* the current version, etc.
* <li>{@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()}
* is enabled.
* </ul>
* </ul>
*
* @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<Task>() {
@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<String, Object> 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<String, Object> 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));
}
});
}
}
}

@ -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.
*
* <p/>
*
* When the {@link JavaPlugin Java plugin} is applied:
*
* <ul>
* <li>{@code sourceCompatibility} is set to {@code 1.8}
* <li>Spring Java Format and Checkstyle plugins are applied
* <li>{@link Test} tasks are configured to use JUnit Platform and use a max heap of 1024M
* <li>{@link JavaCompile} tasks are configured to use UTF-8 encoding
* <li>{@link Javadoc} tasks are configured to use UTF-8 encoding
* <li>{@link Jar} tasks are configured to have the following manifest entries:
* <ul>
* <li>{@code Automatic-Module-Name}
* <li>{@code Build-Jdk-Spec}
* <li>{@code Built-By}
* <li>{@code Implementation-Title}
* <li>{@code Implementation-Version}
* </ul>
* </ul>
*
* <p/>
*
* When the {@link MavenPublishPlugin Maven Publish plugin} is applied:
*
* <ul>
* <li>If the {@code deploymentRepository} property has been set, a
* {@link MavenArtifactRepository Maven artifact repository} is configured to publish to
* it.
* <li>The poms of all {@link MavenPublication Maven publications} are customized to meet
* Maven Central's requirements.
* <li>If the {@link JavaPlugin Java plugin} has also been applied, creation of Javadoc
* and source jars is enabled.
* </ul>
*
* <p/>
*
* When the {@link AsciidoctorJPlugin} is applied, the conventions in
* {@link AsciidoctorConventions} are applied.
*
* @author Andy Wilkinson
*/
public class ConventionsPlugin implements Plugin<Project> {
@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<String, Object> 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");
}
}

@ -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<Project> {
/**
* 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)));
}
}

@ -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<Project> {
/**
* 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<String, String> 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<Task> {
private final File location;
private CleanAction(File location) {
this.location = location;
}
@Override
public void execute(Task task) {
task.getProject().delete(this.location);
}
}
}

@ -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<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring.factories"));
dependsOn((Callable<String>) () -> 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;
}
}

@ -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:
*
* <ul>
* <li>Applying the {@link ConfigurationPropertiesPlugin}.
* <li>Adding a dependency on the auto-configuration annotation processor.
* <li>Defining a task that produces metadata describing the auto-configuration. The
* metadata is made available as an artifact in the
* </ul>
*
* @author Andy Wilkinson
*/
public class AutoConfigurationPlugin implements Plugin<Project> {
/**
* 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<File>) task::getOutputFile), (artifact) -> artifact.builtBy(task));
});
});
}
}

@ -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<AutoConfigurationClass> classes;
private AutoConfiguration(String module, Set<String> 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<AutoConfigurationClass> {
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);
}
}
}

@ -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<String, DependencyVersion> properties = new LinkedHashMap<>();
private final Map<String, String> artifactVersionProperties = new HashMap<>();
private final List<Library> libraries = new ArrayList<Library>();
private final DependencyHandler dependencyHandler;
private final UpgradeHandler upgradeHandler = new UpgradeHandler();
public BomExtension(DependencyHandler dependencyHandler) {
this.dependencyHandler = dependencyHandler;
}
public List<Library> 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<String, DependencyVersion> 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<Group> groups = new ArrayList<>();
private final List<ProhibitedVersion> 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<Module> modules = new ArrayList<>();
private List<String> imports = new ArrayList<>();
private List<String> plugins = new ArrayList<>();
public GroupHandler(String id) {
this.id = id;
}
public void setModules(List<Object> modules) {
this.modules = modules.stream()
.map((input) -> (input instanceof Module) ? (Module) input : new Module((String) input))
.collect(Collectors.toList());
}
public void setImports(List<String> imports) {
this.imports = imports;
}
public void setPlugins(List<String> 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<Exclusion> exclusions = new ArrayList<>();
public void exclude(Map<String, String> 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<String> issueLabels;
public void setOrganization(String organization) {
this.organization = organization;
}
public void setRepository(String repository) {
this.repository = repository;
}
public void setIssueLabels(List<String> issueLabels) {
this.issueLabels = issueLabels;
}
}
public static final class GitHub {
private String organization = "spring-projects";
private String repository = "spring-boot";
private List<String> issueLabels;
private GitHub(String organization, String repository, List<String> issueLabels) {
this.organization = organization;
this.repository = repository;
this.issueLabels = issueLabels;
}
public String getOrganization() {
return this.organization;
}
public String getRepository() {
return this.repository;
}
public List<String> getIssueLabels() {
return this.issueLabels;
}
}
}

@ -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<Project> {
@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<Node> findChildren(Node parent, String name) {
return (List<Node>) 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<Task> {
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);
}
}
}
}

@ -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<String> 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<String> exclusions = module.getExclusions().stream()
.map((exclusion) -> exclusion.getGroupId() + ":" + exclusion.getArtifactId())
.collect(Collectors.toSet());
Set<String> 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);
}
}
}

@ -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<Group> groups;
private final String versionProperty;
private final List<ProhibitedVersion> 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<Group> groups,
List<ProhibitedVersion> 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<Group> getGroups() {
return this.groups;
}
public String getVersionProperty() {
return this.versionProperty;
}
public List<ProhibitedVersion> 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<Module> modules;
private final List<String> plugins;
private final List<String> boms;
public Group(String id, List<Module> modules, List<String> plugins, List<String> boms) {
this.id = id;
this.modules = modules;
this.plugins = plugins;
this.boms = boms;
}
public String getId() {
return this.id;
}
public List<Module> getModules() {
return this.modules;
}
public List<String> getPlugins() {
return this.plugins;
}
public List<String> getBoms() {
return this.boms;
}
}
/**
* A module in a group.
*/
public static class Module {
private final String name;
private final List<Exclusion> exclusions;
public Module(String name) {
this(name, Collections.emptyList());
}
public Module(String name, List<Exclusion> exclusions) {
this.name = name;
this.exclusions = exclusions;
}
public String getName() {
return this.name;
}
public List<Exclusion> 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;
}
}
}

@ -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<DependencyVersion, DependencyVersion> {
/**
* 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<DependencyVersion, DependencyVersion> delegate;
UpgradePolicy(BiPredicate<DependencyVersion, DependencyVersion> delegate) {
this.delegate = delegate;
}
@Override
public boolean test(DependencyVersion candidate, DependencyVersion current) {
return this.delegate.test(candidate, current);
}
}

@ -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<Upgrade> resolveUpgrades(Collection<Library> libraries) {
return libraries.stream().map(this::resolveUpgrade).filter((upgrade) -> upgrade != null)
.collect(Collectors.toList());
}
private Upgrade resolveUpgrade(Library library) {
Map<String, SortedSet<DependencyVersion>> 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<DependencyVersion> allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct()
.filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
.collect(Collectors.toList());
if (allVersions.isEmpty()) {
return null;
}
List<VersionOption> 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<ProhibitedVersion> prohibitedVersions) {
if (prohibitedVersions.isEmpty()) {
return true;
}
for (ProhibitedVersion prohibitedVersion : prohibitedVersions) {
if (prohibitedVersion.getRange()
.containsVersion(new DefaultArtifactVersion(dependencyVersion.toString()))) {
return false;
}
}
return true;
}
private List<String> getMissingModules(Map<String, SortedSet<DependencyVersion>> moduleVersions,
DependencyVersion version) {
List<String> missingModules = new ArrayList<>();
moduleVersions.forEach((name, versions) -> {
if (!versions.contains(version)) {
missingModules.add(name);
}
});
return missingModules;
}
private SortedSet<DependencyVersion> getLaterVersionsForModule(String groupId, String artifactId,
DependencyVersion currentVersion) {
SortedSet<DependencyVersion> 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<String> missingModules;
private VersionOption(DependencyVersion version, List<String> 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, ", ") + ")";
}
}
}

@ -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<String> repositoryUrls;
MavenMetadataVersionResolver(List<String> repositoryUrls) {
this(new RestTemplate(Arrays.asList(new StringHttpMessageConverter())), repositoryUrls);
}
MavenMetadataVersionResolver(RestTemplate restTemplate, List<String> repositoryUrls) {
this.rest = restTemplate;
this.repositoryUrls = repositoryUrls;
}
@Override
public SortedSet<DependencyVersion> resolveVersions(String groupId, String artifactId) {
Set<String> versions = new HashSet<String>();
for (String repositoryUrl : this.repositoryUrls) {
versions.addAll(resolveVersions(groupId, artifactId, repositoryUrl));
}
return new TreeSet<>(versions.stream().map(DependencyVersion::parse).collect(Collectors.toSet()));
}
private Set<String> resolveVersions(String groupId, String artifactId, String repositoryUrl) {
Set<String> versions = new HashSet<String>();
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;
}
}

@ -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;
}
}

@ -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<String> availableLabels = repository.getLabels();
List<String> issueLabels = this.bom.getUpgrade().getGitHub().getIssueLabels();
if (!availableLabels.containsAll(issueLabels)) {
List<String> unknownLabels = new ArrayList<>(issueLabels);
unknownLabels.removeAll(availableLabels);
throw new InvalidUserDataException(
"Unknown label(s): " + StringUtils.collectionToCommaDelimitedString(unknownLabels));
}
Milestone milestone = determineMilestone(repository);
List<Upgrade> 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<Milestone> milestones = repository.getMilestones();
Optional<Milestone> matchingMilestone = milestones.stream()
.filter((milestone) -> milestone.getName().equals(this.milestone)).findFirst();
if (!matchingMilestone.isPresent()) {
throw new InvalidUserDataException("Unknown milestone: " + this.milestone);
}
return matchingMilestone.get();
}
}

@ -0,0 +1,38 @@
/*
* 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.Collection;
import java.util.List;
import org.springframework.boot.build.bom.Library;
/**
* Resolves upgrades for the libraries in a bom.
*
* @author Andy Wilkinson
*/
interface UpgradeResolver {
/**
* Resolves the upgrades to be applied to the given {@code libraries}.
* @param libraries the libraries
* @return the upgrades
*/
List<Upgrade> resolveUpgrades(Collection<Library> libraries);
}

@ -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<DependencyVersion> resolveVersions(String groupId, String artifactId);
}

@ -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);
}
}

@ -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<String> labels, Milestone milestone);
/**
* Returns the labels in the repository.
* @return the labels
*/
List<String> getLabels();
/**
* Returns the milestones in the repository.
* @return the milestones
*/
List<Milestone> getMilestones();
}

@ -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 + ")";
}
}

@ -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);
}
}

@ -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<String> labels, Milestone milestone) {
Map<String, Object> body = new HashMap<>();
body.put("title", title);
if (milestone != null) {
body.put("milestone", milestone.getNumber());
}
if (!labels.isEmpty()) {
body.put("labels", labels);
}
ResponseEntity<Map> response = this.rest.postForEntity("issues", body, Map.class);
return (Integer) response.getBody().get("number");
}
@Override
public List<String> getLabels() {
return get("labels?per_page=100", (label) -> (String) label.get("name"));
}
@Override
public List<Milestone> getMilestones() {
return get("milestones?per_page=100",
(milestone) -> new Milestone((String) milestone.get("title"), (Integer) milestone.get("number")));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private <T> List<T> get(String name, Function<Map<String, Object>, T> mapper) {
ResponseEntity<List> response = this.rest.getForEntity(name, List.class);
List<Map<String, Object>> body = response.getBody();
return body.stream().map(mapper).collect(Collectors.toList());
}
}

@ -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();
}
}

@ -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<ArtifactVersionDependencyVersion> 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);
}
}

@ -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);
}
}

@ -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<DependencyVersion> {
/**
* 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<Function<String, DependencyVersion>> parsers = Arrays.asList(ArtifactVersionDependencyVersion::parse,
ReleaseTrainDependencyVersion::parse, NumericQualifierDependencyVersion::parse,
CombinedPatchAndQualifierDependencyVersion::parse, LeadingZeroesDependencyVersion::parse,
UnstructuredDependencyVersion::parse);
for (Function<String, DependencyVersion> parser : parsers) {
DependencyVersion result = parser.apply(version);
if (result != null) {
return result;
}
}
throw new IllegalArgumentException("Version '" + version + "' could not be parsed");
}
}

@ -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);
}
}

@ -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;
}
}

@ -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);
}
}

@ -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);
}
}

@ -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<Predicate<String>> 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<String, List<String>> 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<String> predicate) {
this.ignores.add(predicate);
}
private static final class ClasspathContents {
private static final Set<String> IGNORED_NAMES = new HashSet<>(Arrays.asList("about.html", "changelog.txt",
"LICENSE", "license.txt", "module-info.class", "notice.txt", "readme.txt"));
private final Map<String, List<String>> classpathContents = new HashMap<>();
private void add(String name, String source) {
this.classpathContents.computeIfAbsent(name, (key) -> new ArrayList<>()).add(source);
}
private Map<String, List<String>> getConflicts(List<Predicate<String>> 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<Predicate<String>> ignores) {
if (name.startsWith("META-INF/")) {
return false;
}
for (String ignoredName : IGNORED_NAMES) {
if (name.equals(ignoredName)) {
return false;
}
}
for (Predicate<String> ignore : ignores) {
if (ignore.test(name)) {
return false;
}
}
return true;
}
}
}

@ -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<String> 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;
}
}

@ -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<RegularFile> 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<RegularFile> 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<String, Object> additionalProperties) {
getProject().copy((copy) -> {
copy.from(this.template);
copy.into(this.outputDir);
Map<String, Object> 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";
}
}

@ -0,0 +1,35 @@
/*
* 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.cli;
import java.util.Collections;
import org.gradle.api.tasks.TaskAction;
/**
* A {@Task} for creating a Homebrew formula manifest.
*
* @author Andy Wilkinson
*/
public class HomebrewFormula extends AbstractPackageManagerDefinitionTask {
@TaskAction
void createFormula() {
createDescriptor(Collections.emptyMap());
}
}

@ -0,0 +1,36 @@
/*
* 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.cli;
import java.util.Collections;
import org.gradle.api.tasks.TaskAction;
/**
* A {@Task} for creating a Scoop manifest.
*
* @author Andy Wilkinson
*/
public class ScoopManifest extends AbstractPackageManagerDefinitionTask {
@TaskAction
void createManifest() {
String version = getProject().getVersion().toString();
createDescriptor(Collections.singletonMap("scoopVersion", version.substring(0, version.lastIndexOf('.'))));
}
}

@ -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<ConstrainedVersion> constrainedVersions;
private File outputFile;
@Inject
public DocumentConstrainedVersions(ObjectFactory objectFactory) {
this.constrainedVersions = objectFactory.setProperty(ConstrainedVersion.class);
}
@Input
public SetProperty<ConstrainedVersion> 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("|===");
}
}
}

@ -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<String, String> versionConstraints = new TreeMap<>();
private final Set<ConstrainedVersion> constrainedVersions = new TreeSet<>();
private final List<String> projectPaths = new ArrayList<String>();
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<String, String> getVersionConstraints() {
return Collections.unmodifiableMap(this.versionConstraints);
}
@Internal
public Set<ConstrainedVersion> 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<ConstrainedVersion>, 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);
}
}
}

@ -0,0 +1,59 @@
/*
* 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;
/**
* Simple builder to help construct Asciidoc markup.
*
* @author Phillip Webb
*/
class AsciidocBuilder {
private final StringBuilder content;
AsciidocBuilder() {
this.content = new StringBuilder();
}
AsciidocBuilder appendKey(Object... items) {
for (Object item : items) {
appendln("`+", item, "+` +");
}
return this;
}
AsciidocBuilder newLine() {
return append(System.lineSeparator());
}
AsciidocBuilder appendln(Object... items) {
return append(items).newLine();
}
AsciidocBuilder append(Object... items) {
for (Object item : items) {
this.content.append(item);
}
return this;
}
@Override
public String toString() {
return this.content.toString();
}
}

@ -0,0 +1,51 @@
/*
* 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.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;
/**
* Table entry regrouping a list of configuration properties sharing the same description.
*
* @author Brian Clozel
*/
class CompoundConfigurationTableEntry extends ConfigurationTableEntry {
private Set<String> configurationKeys;
private String description;
CompoundConfigurationTableEntry(String key, String description) {
this.key = key;
this.description = description;
this.configurationKeys = new TreeSet<>();
}
void addConfigurationKeys(ConfigurationProperty... properties) {
Stream.of(properties).map(ConfigurationProperty::getName).forEach(this.configurationKeys::add);
}
@Override
void write(AsciidocBuilder builder) {
builder.append("|");
this.configurationKeys.forEach(builder::appendKey);
builder.newLine().appendln("|").appendln("|+++", this.description, "+++");
}
}

@ -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.context.properties;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.gradle.api.file.FileCollection;
/**
* Write Asciidoc documents with configuration properties listings.
*
* @author Brian Clozel
* @since 2.0.0
*/
public class ConfigurationMetadataDocumentWriter {
public void writeDocument(Path outputDirectory, DocumentOptions options, FileCollection metadataFiles)
throws IOException {
assertValidOutputDirectory(outputDirectory);
if (!Files.exists(outputDirectory)) {
Files.createDirectory(outputDirectory);
}
List<ConfigurationTable> tables = createConfigTables(ConfigurationProperties.fromFiles(metadataFiles), options);
for (ConfigurationTable table : tables) {
writeConfigurationTable(table, outputDirectory);
}
}
private void assertValidOutputDirectory(Path outputDirPath) {
if (outputDirPath == null) {
throw new IllegalArgumentException("output path should not be null");
}
if (Files.exists(outputDirPath) && !Files.isDirectory(outputDirPath)) {
throw new IllegalArgumentException("output path already exists and is not a directory");
}
}
private List<ConfigurationTable> createConfigTables(Map<String, ConfigurationProperty> metadataProperties,
DocumentOptions options) {
List<ConfigurationTable> tables = new ArrayList<>();
List<String> unmappedKeys = metadataProperties.values().stream().filter((property) -> !property.isDeprecated())
.map(ConfigurationProperty::getName).collect(Collectors.toList());
Map<String, CompoundConfigurationTableEntry> overrides = getOverrides(metadataProperties, unmappedKeys,
options);
options.getMetadataSections().forEach((id, keyPrefixes) -> tables
.add(createConfigTable(metadataProperties, unmappedKeys, overrides, id, keyPrefixes)));
if (!unmappedKeys.isEmpty()) {
throw new IllegalStateException(
"The following keys were not written to the documentation: " + String.join(", ", unmappedKeys));
}
if (!overrides.isEmpty()) {
throw new IllegalStateException("The following keys were not written to the documentation: "
+ String.join(", ", overrides.keySet()));
}
return tables;
}
private Map<String, CompoundConfigurationTableEntry> getOverrides(
Map<String, ConfigurationProperty> metadataProperties, List<String> unmappedKeys, DocumentOptions options) {
Map<String, CompoundConfigurationTableEntry> overrides = new HashMap<>();
options.getOverrides().forEach((keyPrefix, description) -> {
CompoundConfigurationTableEntry entry = new CompoundConfigurationTableEntry(keyPrefix, description);
List<String> matchingKeys = unmappedKeys.stream().filter((key) -> key.startsWith(keyPrefix))
.collect(Collectors.toList());
for (String matchingKey : matchingKeys) {
entry.addConfigurationKeys(metadataProperties.get(matchingKey));
}
overrides.put(keyPrefix, entry);
unmappedKeys.removeAll(matchingKeys);
});
return overrides;
}
private ConfigurationTable createConfigTable(Map<String, ConfigurationProperty> metadataProperties,
List<String> unmappedKeys, Map<String, CompoundConfigurationTableEntry> overrides, String id,
List<String> keyPrefixes) {
ConfigurationTable table = new ConfigurationTable(id);
for (String keyPrefix : keyPrefixes) {
List<String> matchingOverrides = overrides.keySet().stream()
.filter((overrideKey) -> overrideKey.startsWith(keyPrefix)).collect(Collectors.toList());
matchingOverrides.forEach((match) -> table.addEntry(overrides.remove(match)));
}
List<String> matchingKeys = unmappedKeys.stream()
.filter((key) -> keyPrefixes.stream().anyMatch(key::startsWith)).collect(Collectors.toList());
for (String matchingKey : matchingKeys) {
ConfigurationProperty property = metadataProperties.get(matchingKey);
table.addEntry(new SingleConfigurationTableEntry(property));
}
unmappedKeys.removeAll(matchingKeys);
return table;
}
private void writeConfigurationTable(ConfigurationTable table, Path outputDirectory) throws IOException {
Path outputFilePath = outputDirectory.resolve(table.getId() + ".adoc");
Files.deleteIfExists(outputFilePath);
Files.createFile(outputFilePath);
try (OutputStream outputStream = Files.newOutputStream(outputFilePath)) {
outputStream.write(table.toAsciidocTable().getBytes(StandardCharsets.UTF_8));
}
}
}

@ -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<String, ConfigurationProperty> fromFiles(FileCollection files) {
List<ConfigurationProperty> configurationProperties = new ArrayList<>();
try {
Gson gson = new GsonBuilder().create();
for (File file : files) {
try (Reader reader = new FileReader(file)) {
Map<String, Object> json = gson.fromJson(reader, MAP_TYPE);
List<Map<String, Object>> properties = (List<Map<String, Object>>) json.get("properties");
for (Map<String, Object> 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<Map<String, Object>> {
}
}

@ -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:
*
* <ul>
* <li>Adding a dependency on the configuration properties annotation processor.
* <li>Configure the additional metadata locations annotation processor compiler argument
* <li>Defining an artifact for the resulting configuration property metadata so that it
* can be consumed by downstream projects.
* </ul>
*
* @author Andy Wilkinson
*/
public class ConfigurationPropertiesPlugin implements Plugin<Project> {
/**
* 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<File>) () -> 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())));
}
}

@ -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;
}
}

@ -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.context.properties;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
/**
* Asciidoctor table listing configuration properties sharing to a common theme.
*
* @author Brian Clozel
*/
class ConfigurationTable {
private final String id;
private final Set<ConfigurationTableEntry> entries;
ConfigurationTable(String id) {
this.id = id;
this.entries = new TreeSet<>();
}
String getId() {
return this.id;
}
void addEntry(ConfigurationTableEntry... entries) {
this.entries.addAll(Arrays.asList(entries));
}
String toAsciidocTable() {
AsciidocBuilder builder = new AsciidocBuilder();
builder.appendln("[cols=\"1,1,2\", options=\"header\"]");
builder.appendln("|===");
builder.appendln("|Key|Default Value|Description");
builder.appendln();
this.entries.forEach((entry) -> {
entry.write(builder);
builder.appendln();
});
return builder.appendln("|===").toString();
}
}

@ -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.context.properties;
/**
* Abstract class for entries in {@link ConfigurationTable}.
*
* @author Brian Clozel
*/
abstract class ConfigurationTableEntry implements Comparable<ConfigurationTableEntry> {
protected String key;
String getKey() {
return this.key;
}
abstract void write(AsciidocBuilder builder);
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ConfigurationTableEntry other = (ConfigurationTableEntry) obj;
return this.key.equals(other.key);
}
@Override
public int hashCode() {
return this.key.hashCode();
}
@Override
public int compareTo(ConfigurationTableEntry other) {
return this.key.compareTo(other.getKey());
}
}

@ -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);
}
}

@ -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.context.properties;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Options for generating documentation for configuration properties.
*
* @author Brian Clozel
* @since 2.0.0
*/
public final class DocumentOptions {
private final Map<String, List<String>> metadataSections;
private final Map<String, String> overrides;
private DocumentOptions(Map<String, List<String>> metadataSections, Map<String, String> overrides) {
this.metadataSections = metadataSections;
this.overrides = overrides;
}
Map<String, List<String>> getMetadataSections() {
return this.metadataSections;
}
Map<String, String> getOverrides() {
return this.overrides;
}
static Builder builder() {
return new Builder();
}
/**
* Builder for DocumentOptions.
*/
public static class Builder {
Map<String, List<String>> metadataSections = new HashMap<>();
Map<String, String> overrides = new HashMap<>();
SectionSpec addSection(String name) {
return new SectionSpec(this, name);
}
Builder addOverride(String keyPrefix, String description) {
this.overrides.put(keyPrefix, description);
return this;
}
DocumentOptions build() {
return new DocumentOptions(this.metadataSections, this.overrides);
}
}
/**
* Configuration for a documentation section listing properties for a specific theme.
*/
public static class SectionSpec {
private final String name;
private final Builder builder;
SectionSpec(Builder builder, String name) {
this.builder = builder;
this.name = name;
}
Builder withKeyPrefixes(String... keyPrefixes) {
this.builder.metadataSections.put(this.name, Arrays.asList(keyPrefixes));
return this.builder;
}
}
}

@ -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.context.properties;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* Table entry containing a single configuration property.
*
* @author Brian Clozel
*/
class SingleConfigurationTableEntry extends ConfigurationTableEntry {
private final String description;
private final String defaultValue;
SingleConfigurationTableEntry(ConfigurationProperty property) {
this.key = property.getName();
if (property.getType() != null && property.getType().startsWith("java.util.Map")) {
this.key += ".*";
}
this.description = property.getDescription();
this.defaultValue = getDefaultValue(property.getDefaultValue());
}
private String getDefaultValue(Object defaultValue) {
if (defaultValue == null) {
return null;
}
if (defaultValue.getClass().isArray()) {
return Arrays.stream((Object[]) defaultValue).map(Object::toString)
.collect(Collectors.joining("," + System.lineSeparator()));
}
return defaultValue.toString();
}
@Override
void write(AsciidocBuilder builder) {
builder.appendln("|`+", this.key, "+`");
writeDefaultValue(builder);
writeDescription(builder);
builder.appendln();
}
private void writeDefaultValue(AsciidocBuilder builder) {
String defaultValue = (this.defaultValue != null) ? this.defaultValue : "";
if (defaultValue.isEmpty()) {
builder.appendln("|");
}
else {
defaultValue = defaultValue.replace("\\", "\\\\").replace("|", "\\|");
builder.appendln("|`+", defaultValue, "+`");
}
}
private void writeDescription(AsciidocBuilder builder) {
if (this.description == null || this.description.isEmpty()) {
builder.append("|");
}
else {
String cleanedDescription = this.description.replace("|", "\\|");
builder.append("|+++", cleanedDescription, "+++");
}
}
}

@ -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<JavaCompile> {
@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<String, Map<String, Plugin>> 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<String, Plugin> 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<String, Map<String, Plugin>> 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;
}
}
}

@ -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("| <<goals-%s,%s:%s>>%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<Parameter> parameters = mojo.getParameters().stream().filter(Parameter::isEditable)
.collect(Collectors.toList());
List<Parameter> 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<Parameter> 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<Parameter> 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<Parameter> 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();
}
}

@ -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;
}
}

@ -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<Project> {
@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);
}
});
}
}
}
}

@ -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<Mojo> 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<Mojo> parseMojos(Node plugin) throws XPathExpressionException {
List<Mojo> mojos = new ArrayList<Mojo>();
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<Node> nodesAt(String path, Node source) throws XPathExpressionException {
return IterableNodeList.of((NodeList) this.xpath.evaluate(path, source, XPathConstants.NODESET));
}
private List<Parameter> parseParameters(Node mojoNode) throws XPathExpressionException {
Map<String, String> defaultValues = new HashMap<>();
Map<String, String> 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<Parameter> parameters = new ArrayList<>();
for (Node parameterNode : nodesAt("parameters/parameter", mojoNode)) {
parameters.add(parseParameter(parameterNode, defaultValues, userProperties));
}
return parameters;
}
private Parameter parseParameter(Node parameterNode, Map<String, String> defaultValues,
Map<String, String> 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("<code>", "`").replace("</code>", "`").replace("&lt;", "<").replace("&gt;", ">")
.replace("<br>", " ").replace("\n", " ").replace("&quot;", "\"").replaceAll("\\{@code (.*?)\\}", "`$1`")
.replaceAll("\\{@link (.*?)\\}", "`$1`").replaceAll("\\{@literal (.*?)\\}", "`$1`")
.replaceAll("<a href=.\"(.*?)\".>(.*?)</a>", "\\$1[\\$2]");
}
private static final class IterableNodeList implements Iterable<Node> {
private final NodeList nodeList;
private IterableNodeList(NodeList nodeList) {
this.nodeList = nodeList;
}
private static Iterable<Node> of(NodeList nodeList) {
return new IterableNodeList(nodeList);
}
@Override
public Iterator<Node> iterator() {
return new Iterator<Node>() {
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<Mojo> mojos;
private Plugin(String groupId, String artifactId, String version, String goalPrefix, List<Mojo> 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<Mojo> getMojos() {
return this.mojos;
}
}
static final class Mojo {
private final String goal;
private final String description;
private final List<Parameter> parameters;
private Mojo(String goal, String description, List<Parameter> parameters) {
this.goal = goal;
this.description = description;
this.parameters = parameters;
}
String getGoal() {
return this.goal;
}
String getDescription() {
return this.description;
}
List<Parameter> 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;
}
}
}

@ -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<String> versions = new LinkedHashSet<>();
private File outputDir;
@OutputDirectory
public File getOutputDir() {
return this.outputDir;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
@Input
public Set<String> 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())));
}
}
}

@ -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<Project> {
/**
* 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)));
}
}

@ -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<String, String> 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<Starter> 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<Starter> 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<Starter> {
private final String name;
private final String description;
private final Set<String> dependencies;
private Starter(String name, String description, Set<String> 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);
}
}
}

@ -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<String>) () -> getProject().getName());
getInputs().property("description", (Callable<String>) () -> 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);
}
}
}

@ -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<Project> {
@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);
}
}

@ -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<Project> {
/**
* 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;
}
}

@ -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<TestSlice> testSlices = readTestSlices();
writeTable(testSlices);
}
@SuppressWarnings("unchecked")
private Set<TestSlice> readTestSlices() throws IOException {
Set<TestSlice> 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<String>) metadata.propertyNames())) {
testSlices.add(new TestSlice(name,
new TreeSet<>(StringUtils.commaDelimitedListToSet(metadata.getProperty(name)))));
}
}
return testSlices;
}
private void writeTable(Set<TestSlice> 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<TestSlice> {
private final String className;
private final SortedSet<String> importedAutoConfigurations;
private TestSlice(String className, SortedSet<String> importedAutoConfigurations) {
this.className = ClassUtils.getShortName(className);
this.importedAutoConfigurations = importedAutoConfigurations;
}
@Override
public int compareTo(TestSlice other) {
return this.className.compareTo(other.className);
}
}
}

@ -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<File>) () -> this.sourceSet.getOutput().getResourcesDir());
getInputs().files((Callable<FileCollection>) () -> 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<File>) 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<Path> 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<String> getImportedAutoConfiguration(Properties springFactories,
AnnotationMetadata annotationMetadata) {
Stream<String> 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<String> 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");
}
}

@ -0,0 +1,24 @@
<settings>
<localRepository>localRepositoryPath</localRepository>
<profiles>
<profile>
<id>spring-repositories</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</profile>
</profiles>
</settings>

@ -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<NodeAssert, Node> implements AssertProvider<NodeAssert> {
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;
}
}

@ -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<NodeAssert> 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));
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -0,0 +1,47 @@
/*
* 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 org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CompoundConfigurationTableEntry}.
*
* @author Brian Clozel
*/
public class CompoundConfigurationTableEntryTests {
private static String NEWLINE = System.lineSeparator();
@Test
void simpleProperty() {
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);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo(
"|`+spring.test.first+` +" + NEWLINE + "`+spring.test.second+` +" + NEWLINE + "`+spring.test.third+` +"
+ NEWLINE + NEWLINE + "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
}

@ -0,0 +1,48 @@
/*
* 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 org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationTable}.
*
* @author Brian Clozel
*/
public class ConfigurationTableTests {
private static String NEWLINE = System.lineSeparator();
@Test
void simpleTable() {
ConfigurationTable table = new ConfigurationTable("test");
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 + "|==="
+ NEWLINE + "|Key|Default Value|Description" + NEWLINE + NEWLINE + "|`+spring.test.other+`" + NEWLINE
+ "|`+other value+`" + NEWLINE + "|+++This is another description.+++" + NEWLINE + NEWLINE
+ "|`+spring.test.prop+`" + NEWLINE + "|`+something+`" + NEWLINE + "|+++This is a description.+++"
+ NEWLINE + NEWLINE + "|===" + NEWLINE);
}
}

@ -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.context.properties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SingleConfigurationTableEntry}.
*
* @author Brian Clozel
*/
public class SingleConfigurationTableEntryTests {
private static String NEWLINE = System.lineSeparator();
@Test
void simpleProperty() {
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);
assertThat(builder.toString()).isEqualTo("|`+spring.test.prop+`" + NEWLINE + "|`+something+`" + NEWLINE
+ "|+++This is a description.+++" + NEWLINE);
}
@Test
void noDefaultValue() {
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);
assertThat(builder.toString()).isEqualTo(
"|`+spring.test.prop+`" + NEWLINE + "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
@Test
void defaultValueWithPipes() {
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);
assertThat(builder.toString()).isEqualTo("|`+spring.test.prop+`" + NEWLINE + "|`+first\\|second+`" + NEWLINE
+ "|+++This is a description.+++" + NEWLINE);
}
@Test
void defaultValueWithBackslash() {
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);
assertThat(builder.toString()).isEqualTo("|`+spring.test.prop+`" + NEWLINE + "|`+first\\\\second+`" + NEWLINE
+ "|+++This is a description.+++" + NEWLINE);
}
@Test
void descriptionWithPipe() {
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);
assertThat(builder.toString()).isEqualTo("|`+spring.test.prop+`" + NEWLINE + "|" + NEWLINE
+ "|+++This is a description with a \\| pipe.+++" + NEWLINE);
}
@Test
void mapProperty() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
"java.util.Map<java.lang.String,java.lang.String>", null, "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo(
"|`+spring.test.prop.*+`" + NEWLINE + "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
@Test
void listProperty() {
String[] defaultValue = new String[] { "first", "second", "third" };
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
"java.util.List<java.lang.String>", defaultValue, "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo("|`+spring.test.prop+`" + NEWLINE + "|`+first," + NEWLINE + "second,"
+ NEWLINE + "third+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
}

@ -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<String> entryNames) {
Map<String, PluginEntry> 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);
}
}
}

@ -0,0 +1,40 @@
/*
* 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 org.junit.jupiter.api.Test;
import org.springframework.boot.build.mavenplugin.PluginXmlParser.Plugin;
/**
* Tests for {@link PluginXmlParser}.
*
* @author Andy Wilkinson
*/
public class PluginXmlParserTests {
private final PluginXmlParser parser = new PluginXmlParser();
@Test
void dunno() {
Plugin plugin = this.parser.parse(new File("src/test/resources/plugin.xml"));
System.out.println(plugin);
}
}

@ -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();
}
}

@ -0,0 +1,911 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by maven-plugin-tools 3.6 -->
<plugin>
<name>Spring Boot Maven Plugin</name>
<description></description>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.0.GRADLE-SNAPSHOT</version>
<goalPrefix>spring-boot</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
<mojos>
<mojo>
<goal>build-info</goal>
<description>Generate a {@code build-info.properties} file based the content of the current
{@link MavenProject}.</description>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>generate-resources</phase>
<implementation>org.springframework.boot.maven.BuildInfoMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<since>1.4.0</since>
<threadSafe>true</threadSafe>
<parameters>
<parameter>
<name>additionalProperties</name>
<type>java.util.Map</type>
<required>false</required>
<editable>true</editable>
<description>Additional properties to store in the build-info.properties. Each entry is prefixed
by {@code build.} in the generated build-info.properties.</description>
</parameter>
<parameter>
<name>outputFile</name>
<type>java.io.File</type>
<required>false</required>
<editable>true</editable>
<description>The location of the generated build-info.properties.</description>
</parameter>
<parameter>
<name>project</name>
<type>org.apache.maven.project.MavenProject</type>
<required>true</required>
<editable>false</editable>
<description>The Maven project.</description>
</parameter>
<parameter>
<name>session</name>
<type>org.apache.maven.execution.MavenSession</type>
<required>true</required>
<editable>false</editable>
<description>The Maven session.</description>
</parameter>
<parameter>
<name>time</name>
<type>java.lang.String</type>
<since>2.2.0</since>
<required>false</required>
<editable>true</editable>
<description>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 &apos;off&apos;}.</description>
</parameter>
</parameters>
<configuration>
<outputFile implementation="java.io.File" default-value="${project.build.outputDirectory}/META-INF/build-info.properties"/>
<project implementation="org.apache.maven.project.MavenProject" default-value="${project}"/>
<session implementation="org.apache.maven.execution.MavenSession" default-value="${session}"/>
</configuration>
<requirements>
<requirement>
<role>org.sonatype.plexus.build.incremental.BuildContext</role>
<field-name>buildContext</field-name>
</requirement>
</requirements>
</mojo>
<mojo>
<goal>help</goal>
<description>Display help information on spring-boot-maven-plugin.&lt;br&gt;
Call &lt;code&gt;mvn spring-boot:help -Ddetail=true -Dgoal=&amp;lt;goal-name&amp;gt;&lt;/code&gt; to display parameter details.</description>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>false</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<implementation>org.springframework.boot.maven.HelpMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<threadSafe>true</threadSafe>
<parameters>
<parameter>
<name>detail</name>
<type>boolean</type>
<required>false</required>
<editable>true</editable>
<description>If &lt;code&gt;true&lt;/code&gt;, display all settable properties for each goal.</description>
</parameter>
<parameter>
<name>goal</name>
<type>java.lang.String</type>
<required>false</required>
<editable>true</editable>
<description>The name of the goal for which to show help. If unspecified, all goals will be displayed.</description>
</parameter>
<parameter>
<name>indentSize</name>
<type>int</type>
<required>false</required>
<editable>true</editable>
<description>The number of spaces per indentation level, should be positive.</description>
</parameter>
<parameter>
<name>lineLength</name>
<type>int</type>
<required>false</required>
<editable>true</editable>
<description>The maximum length of a display line, should be positive.</description>
</parameter>
</parameters>
<configuration>
<detail implementation="boolean" default-value="false">${detail}</detail>
<goal implementation="java.lang.String">${goal}</goal>
<indentSize implementation="int" default-value="2">${indentSize}</indentSize>
<lineLength implementation="int" default-value="80">${lineLength}</lineLength>
</configuration>
</mojo>
<mojo>
<goal>repackage</goal>
<description>Repackages existing JAR and WAR archives so that they can be executed from the command
line using {@literal java -jar}. With &lt;code&gt;layout=NONE&lt;/code&gt; can also be used simply
to package a JAR with nested dependencies (and no main class, so not executable).</description>
<requiresDependencyResolution>compile+runtime</requiresDependencyResolution>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>package</phase>
<implementation>org.springframework.boot.maven.RepackageMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<since>1.0.0</since>
<requiresDependencyCollection>compile+runtime</requiresDependencyCollection>
<threadSafe>true</threadSafe>
<parameters>
<parameter>
<name>attach</name>
<type>boolean</type>
<since>1.4.0</since>
<required>false</required>
<editable>true</editable>
<description>Attach the repackaged archive to be installed and deployed.</description>
</parameter>
<parameter>
<name>classifier</name>
<type>java.lang.String</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>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 &lt;a href=
&quot;https://maven.apache.org/plugins/maven-deploy-plugin/examples/deploying-with-classifiers.html&quot;
&gt;the Maven documentation for more details&lt;/a&gt;.</description>
</parameter>
<parameter>
<name>embeddedLaunchScript</name>
<type>java.io.File</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>The embedded launch script to prepend to the front of the jar if it is fully
executable. If not specified the &apos;Spring Boot&apos; default script will be used.</description>
</parameter>
<parameter>
<name>embeddedLaunchScriptProperties</name>
<type>java.util.Properties</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>Properties that should be expanded in the embedded launch script.</description>
</parameter>
<parameter>
<name>excludeDevtools</name>
<type>boolean</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>Exclude Spring Boot devtools from the repackaged archive.</description>
</parameter>
<parameter>
<name>excludeGroupIds</name>
<type>java.lang.String</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>Comma separated list of groupId names to exclude (exact match).</description>
</parameter>
<parameter>
<name>excludes</name>
<type>java.util.List</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>executable</name>
<type>boolean</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>Make a fully executable jar for *nix machines by prepending a launch script to the
jar.
&lt;p&gt;
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.</description>
</parameter>
<parameter>
<name>finalName</name>
<type>java.lang.String</type>
<since>1.0.0</since>
<required>false</required>
<editable>false</editable>
<description>Name of the generated archive.</description>
</parameter>
<parameter>
<name>includeSystemScope</name>
<type>boolean</type>
<since>1.4.0</since>
<required>false</required>
<editable>true</editable>
<description>Include system scoped dependencies.</description>
</parameter>
<parameter>
<name>includes</name>
<type>java.util.List</type>
<since>1.2.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>layout</name>
<type>org.springframework.boot.maven.RepackageMojo$LayoutType</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>layoutFactory</name>
<type>org.springframework.boot.loader.tools.LayoutFactory</type>
<since>1.5.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>mainClass</name>
<type>java.lang.String</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>The name of the main class. If not specified the first compiled class found that
contains a &apos;main&apos; method will be used.</description>
</parameter>
<parameter>
<name>outputDirectory</name>
<type>java.io.File</type>
<since>1.0.0</since>
<required>true</required>
<editable>true</editable>
<description>Directory containing the generated archive.</description>
</parameter>
<parameter>
<name>project</name>
<type>org.apache.maven.project.MavenProject</type>
<since>1.0.0</since>
<required>true</required>
<editable>false</editable>
<description>The Maven project.</description>
</parameter>
<parameter>
<name>requiresUnpack</name>
<type>java.util.List</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>A list of the libraries that must be unpacked from fat jars in order to run.
Specify each library as a {@code &lt;dependency&gt;} with a {@code &lt;groupId&gt;} and a
{@code &lt;artifactId&gt;} and they will be unpacked at runtime.</description>
</parameter>
<parameter>
<name>skip</name>
<type>boolean</type>
<since>1.2.0</since>
<required>false</required>
<editable>true</editable>
<description>Skip the execution.</description>
</parameter>
</parameters>
<configuration>
<attach implementation="boolean" default-value="true"/>
<excludeDevtools implementation="boolean" default-value="true">${spring-boot.repackage.excludeDevtools}</excludeDevtools>
<excludeGroupIds implementation="java.lang.String" default-value="">${spring-boot.excludeGroupIds}</excludeGroupIds>
<excludes implementation="java.util.List">${spring-boot.excludes}</excludes>
<executable implementation="boolean" default-value="false"/>
<finalName implementation="java.lang.String" default-value="${project.build.finalName}"/>
<includeSystemScope implementation="boolean" default-value="false"/>
<includes implementation="java.util.List">${spring-boot.includes}</includes>
<layout implementation="org.springframework.boot.maven.RepackageMojo$LayoutType">${spring-boot.repackage.layout}</layout>
<outputDirectory implementation="java.io.File" default-value="${project.build.directory}"/>
<project implementation="org.apache.maven.project.MavenProject" default-value="${project}"/>
<skip implementation="boolean" default-value="false">${spring-boot.repackage.skip}</skip>
</configuration>
<requirements>
<requirement>
<role>org.apache.maven.project.MavenProjectHelper</role>
<field-name>projectHelper</field-name>
</requirement>
</requirements>
</mojo>
<mojo>
<goal>run</goal>
<description>Run an executable archive application.</description>
<requiresDependencyResolution>test</requiresDependencyResolution>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>validate</phase>
<executePhase>test-compile</executePhase>
<implementation>org.springframework.boot.maven.RunMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<since>1.0.0</since>
<threadSafe>false</threadSafe>
<parameters>
<parameter>
<name>addResources</name>
<type>boolean</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>agent</name>
<type>java.io.File[]</type>
<since>1.0.0</since>
<deprecated>since 2.2.0 in favor of {@code agents}</deprecated>
<required>false</required>
<editable>true</editable>
<description>Path to agent jar. NOTE: a forked process is required to use this feature.</description>
</parameter>
<parameter>
<name>agents</name>
<type>java.io.File[]</type>
<since>2.2.0</since>
<required>false</required>
<editable>true</editable>
<description>Path to agent jars. NOTE: a forked process is required to use this feature.</description>
</parameter>
<parameter>
<name>arguments</name>
<type>java.lang.String[]</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>Arguments that should be passed to the application. On command line use commas to
separate multiple arguments.</description>
</parameter>
<parameter>
<name>classesDirectory</name>
<type>java.io.File</type>
<since>1.0.0</since>
<required>true</required>
<editable>true</editable>
<description>Directory containing the classes and resource files that should be packaged into
the archive.</description>
</parameter>
<parameter>
<name>environmentVariables</name>
<type>java.util.Map</type>
<since>2.1.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>excludeGroupIds</name>
<type>java.lang.String</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>Comma separated list of groupId names to exclude (exact match).</description>
</parameter>
<parameter>
<name>excludes</name>
<type>java.util.List</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>folders</name>
<type>java.lang.String[]</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>Additional folders besides the classes directory that should be added to the
classpath.</description>
</parameter>
<parameter>
<name>fork</name>
<type>boolean</type>
<since>1.2.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>includes</name>
<type>java.util.List</type>
<since>1.2.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>jvmArguments</name>
<type>java.lang.String</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>mainClass</name>
<type>java.lang.String</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>The name of the main class. If not specified the first compiled class found that
contains a &apos;main&apos; method will be used.</description>
</parameter>
<parameter>
<name>noverify</name>
<type>boolean</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>Flag to say that the agent requires -noverify.</description>
</parameter>
<parameter>
<name>optimizedLaunch</name>
<type>boolean</type>
<since>2.2.0</since>
<required>false</required>
<editable>true</editable>
<description>Whether the JVM&apos;s launch should be optimized.</description>
</parameter>
<parameter>
<name>profiles</name>
<type>java.lang.String[]</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>The spring profiles to activate. Convenience shortcut of specifying the
&apos;spring.profiles.active&apos; argument. On command line use commas to separate multiple
profiles.</description>
</parameter>
<parameter>
<name>project</name>
<type>org.apache.maven.project.MavenProject</type>
<since>1.0.0</since>
<required>true</required>
<editable>false</editable>
<description>The Maven project.</description>
</parameter>
<parameter>
<name>skip</name>
<type>boolean</type>
<since>1.3.2</since>
<required>false</required>
<editable>true</editable>
<description>Skip the execution.</description>
</parameter>
<parameter>
<name>systemPropertyVariables</name>
<type>java.util.Map</type>
<since>2.1.0</since>
<required>false</required>
<editable>true</editable>
<description>List of JVM system properties to pass to the process. NOTE: a forked process is
required to use this feature.</description>
</parameter>
<parameter>
<name>useTestClasspath</name>
<type>java.lang.Boolean</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>Flag to include the test classpath when running.</description>
</parameter>
<parameter>
<name>workingDirectory</name>
<type>java.io.File</type>
<since>1.5.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
</parameters>
<configuration>
<addResources implementation="boolean" default-value="false">${spring-boot.run.addResources}</addResources>
<agent implementation="java.io.File[]">${spring-boot.run.agent}</agent>
<agents implementation="java.io.File[]">${spring-boot.run.agents}</agents>
<arguments implementation="java.lang.String[]">${spring-boot.run.arguments}</arguments>
<classesDirectory implementation="java.io.File" default-value="${project.build.outputDirectory}"/>
<excludeGroupIds implementation="java.lang.String" default-value="">${spring-boot.excludeGroupIds}</excludeGroupIds>
<excludes implementation="java.util.List">${spring-boot.excludes}</excludes>
<folders implementation="java.lang.String[]">${spring-boot.run.folders}</folders>
<fork implementation="boolean" default-value="true">${spring-boot.run.fork}</fork>
<includes implementation="java.util.List">${spring-boot.includes}</includes>
<jvmArguments implementation="java.lang.String">${spring-boot.run.jvmArguments}</jvmArguments>
<mainClass implementation="java.lang.String">${spring-boot.run.main-class}</mainClass>
<noverify implementation="boolean">${spring-boot.run.noverify}</noverify>
<optimizedLaunch implementation="boolean" default-value="true">${spring-boot.run.optimizedLaunch}</optimizedLaunch>
<profiles implementation="java.lang.String[]">${spring-boot.run.profiles}</profiles>
<project implementation="org.apache.maven.project.MavenProject" default-value="${project}"/>
<skip implementation="boolean" default-value="false">${spring-boot.run.skip}</skip>
<useTestClasspath implementation="java.lang.Boolean" default-value="false">${spring-boot.run.useTestClasspath}</useTestClasspath>
<workingDirectory implementation="java.io.File">${spring-boot.run.workingDirectory}</workingDirectory>
</configuration>
</mojo>
<mojo>
<goal>start</goal>
<description>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.</description>
<requiresDependencyResolution>test</requiresDependencyResolution>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>pre-integration-test</phase>
<implementation>org.springframework.boot.maven.StartMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<since>1.3.0</since>
<threadSafe>false</threadSafe>
<parameters>
<parameter>
<name>addResources</name>
<type>boolean</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>agent</name>
<type>java.io.File[]</type>
<since>1.0.0</since>
<deprecated>since 2.2.0 in favor of {@code agents}</deprecated>
<required>false</required>
<editable>true</editable>
<description>Path to agent jar. NOTE: a forked process is required to use this feature.</description>
</parameter>
<parameter>
<name>agents</name>
<type>java.io.File[]</type>
<since>2.2.0</since>
<required>false</required>
<editable>true</editable>
<description>Path to agent jars. NOTE: a forked process is required to use this feature.</description>
</parameter>
<parameter>
<name>arguments</name>
<type>java.lang.String[]</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>Arguments that should be passed to the application. On command line use commas to
separate multiple arguments.</description>
</parameter>
<parameter>
<name>classesDirectory</name>
<type>java.io.File</type>
<since>1.0.0</since>
<required>true</required>
<editable>true</editable>
<description>Directory containing the classes and resource files that should be packaged into
the archive.</description>
</parameter>
<parameter>
<name>environmentVariables</name>
<type>java.util.Map</type>
<since>2.1.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>excludeGroupIds</name>
<type>java.lang.String</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>Comma separated list of groupId names to exclude (exact match).</description>
</parameter>
<parameter>
<name>excludes</name>
<type>java.util.List</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>folders</name>
<type>java.lang.String[]</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>Additional folders besides the classes directory that should be added to the
classpath.</description>
</parameter>
<parameter>
<name>fork</name>
<type>boolean</type>
<since>1.2.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>includes</name>
<type>java.util.List</type>
<since>1.2.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>jmxName</name>
<type>java.lang.String</type>
<required>false</required>
<editable>true</editable>
<description>The JMX name of the automatically deployed MBean managing the lifecycle of the
spring application.</description>
</parameter>
<parameter>
<name>jmxPort</name>
<type>int</type>
<required>false</required>
<editable>true</editable>
<description>The port to use to expose the platform MBeanServer if the application is forked.</description>
</parameter>
<parameter>
<name>jvmArguments</name>
<type>java.lang.String</type>
<since>1.1.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>mainClass</name>
<type>java.lang.String</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>The name of the main class. If not specified the first compiled class found that
contains a &apos;main&apos; method will be used.</description>
</parameter>
<parameter>
<name>maxAttempts</name>
<type>int</type>
<required>false</required>
<editable>true</editable>
<description>The maximum number of attempts to check if the spring application is ready.
Combined with the &quot;wait&quot; argument, this gives a global timeout value (30 sec by
default)</description>
</parameter>
<parameter>
<name>noverify</name>
<type>boolean</type>
<since>1.0.0</since>
<required>false</required>
<editable>true</editable>
<description>Flag to say that the agent requires -noverify.</description>
</parameter>
<parameter>
<name>profiles</name>
<type>java.lang.String[]</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>The spring profiles to activate. Convenience shortcut of specifying the
&apos;spring.profiles.active&apos; argument. On command line use commas to separate multiple
profiles.</description>
</parameter>
<parameter>
<name>project</name>
<type>org.apache.maven.project.MavenProject</type>
<since>1.0.0</since>
<required>true</required>
<editable>false</editable>
<description>The Maven project.</description>
</parameter>
<parameter>
<name>skip</name>
<type>boolean</type>
<since>1.3.2</since>
<required>false</required>
<editable>true</editable>
<description>Skip the execution.</description>
</parameter>
<parameter>
<name>systemPropertyVariables</name>
<type>java.util.Map</type>
<since>2.1.0</since>
<required>false</required>
<editable>true</editable>
<description>List of JVM system properties to pass to the process. NOTE: a forked process is
required to use this feature.</description>
</parameter>
<parameter>
<name>useTestClasspath</name>
<type>java.lang.Boolean</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>Flag to include the test classpath when running.</description>
</parameter>
<parameter>
<name>wait</name>
<type>long</type>
<required>false</required>
<editable>true</editable>
<description>The number of milli-seconds to wait between each attempt to check if the spring
application is ready.</description>
</parameter>
<parameter>
<name>workingDirectory</name>
<type>java.io.File</type>
<since>1.5.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
</parameters>
<configuration>
<addResources implementation="boolean" default-value="false">${spring-boot.run.addResources}</addResources>
<agent implementation="java.io.File[]">${spring-boot.run.agent}</agent>
<agents implementation="java.io.File[]">${spring-boot.run.agents}</agents>
<arguments implementation="java.lang.String[]">${spring-boot.run.arguments}</arguments>
<classesDirectory implementation="java.io.File" default-value="${project.build.outputDirectory}"/>
<excludeGroupIds implementation="java.lang.String" default-value="">${spring-boot.excludeGroupIds}</excludeGroupIds>
<excludes implementation="java.util.List">${spring-boot.excludes}</excludes>
<folders implementation="java.lang.String[]">${spring-boot.run.folders}</folders>
<fork implementation="boolean" default-value="true">${spring-boot.run.fork}</fork>
<includes implementation="java.util.List">${spring-boot.includes}</includes>
<jvmArguments implementation="java.lang.String">${spring-boot.run.jvmArguments}</jvmArguments>
<mainClass implementation="java.lang.String">${spring-boot.run.main-class}</mainClass>
<noverify implementation="boolean">${spring-boot.run.noverify}</noverify>
<profiles implementation="java.lang.String[]">${spring-boot.run.profiles}</profiles>
<project implementation="org.apache.maven.project.MavenProject" default-value="${project}"/>
<skip implementation="boolean" default-value="false">${spring-boot.run.skip}</skip>
<useTestClasspath implementation="java.lang.Boolean" default-value="false">${spring-boot.run.useTestClasspath}</useTestClasspath>
<workingDirectory implementation="java.io.File">${spring-boot.run.workingDirectory}</workingDirectory>
</configuration>
</mojo>
<mojo>
<goal>stop</goal>
<description>Stop a spring application that has been started by the &quot;start&quot; goal. Typically invoked
once a test suite has completed.</description>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>post-integration-test</phase>
<implementation>org.springframework.boot.maven.StopMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<since>1.3.0</since>
<threadSafe>false</threadSafe>
<parameters>
<parameter>
<name>fork</name>
<type>java.lang.Boolean</type>
<since>1.3.0</since>
<required>false</required>
<editable>true</editable>
<description>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.</description>
</parameter>
<parameter>
<name>jmxName</name>
<type>java.lang.String</type>
<required>false</required>
<editable>true</editable>
<description>The JMX name of the automatically deployed MBean managing the lifecycle of the
application.</description>
</parameter>
<parameter>
<name>jmxPort</name>
<type>int</type>
<required>false</required>
<editable>true</editable>
<description>The port to use to lookup the platform MBeanServer if the application has been
forked.</description>
</parameter>
<parameter>
<name>project</name>
<type>org.apache.maven.project.MavenProject</type>
<since>1.4.1</since>
<required>true</required>
<editable>false</editable>
<description>The Maven project.</description>
</parameter>
<parameter>
<name>skip</name>
<type>boolean</type>
<since>1.3.2</since>
<required>false</required>
<editable>true</editable>
<description>Skip the execution.</description>
</parameter>
</parameters>
<configuration>
<fork implementation="java.lang.Boolean">${spring-boot.stop.fork}</fork>
<project implementation="org.apache.maven.project.MavenProject" default-value="${project}"/>
<skip implementation="boolean" default-value="false">${spring-boot.stop.skip}</skip>
</configuration>
</mojo>
</mojos>
<dependencies/>
</plugin>

@ -3,7 +3,7 @@ resource_types:
type: docker-image type: docker-image
source: source:
repository: springio/artifactory-resource repository: springio/artifactory-resource
tag: 0.0.10 tag: 0.0.11-SNAPSHOT
- name: pull-request - name: pull-request
type: docker-image type: docker-image
source: source:
@ -209,19 +209,9 @@ jobs:
timeout: ((task-timeout)) timeout: ((task-timeout))
image: spring-boot-ci-image image: spring-boot-ci-image
file: git-repo/ci/tasks/build-project.yml file: git-repo/ci/tasks/build-project.yml
- in_parallel: params:
- task: build-smoke-tests GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
timeout: ((task-timeout)) GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
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_failure: on_failure:
do: do:
- put: repo-status-build - 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_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}" build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
disable_checksum_uploads: true disable_checksum_uploads: true
exclude: threads: 8
- "**/*.effective-pom"
- "**/spring-boot-configuration-docs/**"
- "**/spring-boot-test-support/**"
artifact_set: artifact_set:
- include: - include:
- "/**/spring-boot-docs-*.zip" - "/**/spring-boot-docs-*.zip"
properties: properties:
"zip.type": "docs" "zip.type": "docs"
"zip.deployed": "false" "zip.deployed": "false"
get_params:
threads: 8
on_failure: on_failure:
do: do:
- put: slack-alert - put: slack-alert
@ -283,19 +272,6 @@ jobs:
timeout: ((task-timeout)) timeout: ((task-timeout))
image: spring-boot-ci-image image: spring-boot-ci-image
file: git-repo/ci/tasks/build-pr-project.yml 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: on_success:
put: git-pull-request put: git-pull-request
params: params:
@ -321,19 +297,9 @@ jobs:
timeout: ((task-timeout)) timeout: ((task-timeout))
image: spring-boot-jdk11-ci-image image: spring-boot-jdk11-ci-image
file: git-repo/ci/tasks/build-project.yml file: git-repo/ci/tasks/build-project.yml
- in_parallel: params:
- task: build-smoke-tests GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
timeout: ((task-timeout)) GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
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
on_failure: on_failure:
do: do:
- put: repo-status-jdk11-build - put: repo-status-jdk11-build
@ -362,24 +328,14 @@ jobs:
- put: repo-status-jdk13-build - put: repo-status-jdk13-build
params: { state: "pending", commit: "git-repo" } params: { state: "pending", commit: "git-repo" }
- do: - do:
- task: build-project - task: build-project
privileged: true privileged: true
timeout: ((task-timeout)) timeout: ((task-timeout))
image: spring-boot-jdk13-ci-image image: spring-boot-jdk13-ci-image
file: git-repo/ci/tasks/build-project.yml file: git-repo/ci/tasks/build-project.yml
- in_parallel: params:
- task: build-smoke-tests GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
timeout: ((task-timeout)) GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
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
on_failure: on_failure:
do: do:
- put: repo-status-jdk13-build - put: repo-status-jdk13-build
@ -412,6 +368,9 @@ jobs:
tags: tags:
- WIN64 - WIN64
timeout: ((task-timeout)) timeout: ((task-timeout))
params:
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
on_failure: on_failure:
do: do:
- put: slack-alert - put: slack-alert
@ -437,6 +396,8 @@ jobs:
file: git-repo/ci/tasks/stage.yml file: git-repo/ci/tasks/stage.yml
params: params:
RELEASE_TYPE: M RELEASE_TYPE: M
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
- put: artifactory-repo - put: artifactory-repo
params: params:
<<: *artifactory-params <<: *artifactory-params
@ -455,6 +416,8 @@ jobs:
file: git-repo/ci/tasks/stage.yml file: git-repo/ci/tasks/stage.yml
params: params:
RELEASE_TYPE: RC RELEASE_TYPE: RC
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
- put: artifactory-repo - put: artifactory-repo
params: params:
<<: *artifactory-params <<: *artifactory-params
@ -473,6 +436,8 @@ jobs:
file: git-repo/ci/tasks/stage.yml file: git-repo/ci/tasks/stage.yml
params: params:
RELEASE_TYPE: RELEASE RELEASE_TYPE: RELEASE
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle-enterprise-cache-username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle-enterprise-cache-password))
- put: artifactory-repo - put: artifactory-repo
params: params:
<<: *artifactory-params <<: *artifactory-params

@ -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

@ -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

@ -1,6 +1,4 @@
SET "JAVA_HOME=C:\opt\jdk-8" SET "JAVA_HOME=C:\opt\jdk-8"
SET PATH=%PATH%;C:\Program Files\Git\usr\bin SET PATH=%PATH%;C:\Program Files\Git\usr\bin
cd git-repo cd git-repo
.\gradlew --no-daemon --max-workers=4 build
echo ".\mvnw clean install" > build.log
.\mvnw clean install -U >> build.log 2>&1 || (sleep 1 && tail -n 3000 build.log && exit 1)

@ -5,6 +5,5 @@ source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null pushd git-repo > /dev/null
run_maven -N clean verify ./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
run_maven -f spring-boot-project/pom.xml clean deploy -U -Dfull -DaltDeploymentRepository=distribution::default::file://${repository}
popd > /dev/null popd > /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

@ -12,7 +12,7 @@ git clone git-repo stage-git-repo > /dev/null
pushd 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 if [[ $RELEASE_TYPE = "M" ]]; then
stageVersion=$( get_next_milestone_release $snapshotVersion) stageVersion=$( get_next_milestone_release $snapshotVersion)
nextVersion=$snapshotVersion nextVersion=$snapshotVersion
@ -27,23 +27,20 @@ else
fi fi
echo "Staging $stageVersion (next version will be $nextVersion)" 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.name "Spring Buildmaster" > /dev/null
git config user.email "buildmaster@springframework.org" > /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 commit -m"Release v$stageVersion" > /dev/null
git tag -a "v$stageVersion" -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} ./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
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}
git reset --hard HEAD^ > /dev/null git reset --hard HEAD^ > /dev/null
if [[ $nextVersion != $snapshotVersion ]]; then if [[ $nextVersion != $snapshotVersion ]]; then
echo "Setting next development version (v$nextVersion)" 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 add pom.xml > /dev/null
git commit -m"Next development version (v$nextVersion)" > /dev/null git commit -m"Next development version (v$nextVersion)" > /dev/null
fi; fi;

@ -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

@ -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

@ -7,5 +7,8 @@ outputs:
caches: caches:
- path: maven - path: maven
- path: gradle - path: gradle
params:
CI: true
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run: run:
path: git-repo/ci/scripts/build-project.sh path: git-repo/ci/scripts/build-project.sh

@ -8,6 +8,11 @@ caches:
- path: maven - path: maven
- path: gradle - path: gradle
- path: embedmongo - path: embedmongo
params:
CI: true
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run: run:
path: bash path: bash
args: args:
@ -16,4 +21,3 @@ run:
source /docker-lib.sh source /docker-lib.sh
start_docker start_docker
${PWD}/git-repo/ci/scripts/build-project.sh ${PWD}/git-repo/ci/scripts/build-project.sh

@ -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

@ -7,6 +7,10 @@ outputs:
- name: distribution-repository - name: distribution-repository
params: params:
RELEASE_TYPE: RELEASE_TYPE:
CI: true
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
caches: caches:
- path: maven - path: maven
- path: gradle - path: gradle

@ -4,13 +4,12 @@
xmlns:xmi="http://www.omg.org/XMI" xmlns:xmi="http://www.omg.org/XMI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdt="http://www.eclipse.org/oomph/setup/jdt/1.0" 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:predicates="http://www.eclipse.org/oomph/predicates/1.0"
xmlns:setup="http://www.eclipse.org/oomph/setup/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.p2="http://www.eclipse.org/oomph/setup/p2/1.0"
xmlns:setup.workingsets="http://www.eclipse.org/oomph/setup/workingsets/1.0" xmlns:setup.workingsets="http://www.eclipse.org/oomph/setup/workingsets/1.0"
xmlns:workingsets="http://www.eclipse.org/oomph/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" name="spring.boot.2.3.x"
label="Spring Boot 2.3.x"> label="Spring Boot 2.3.x">
<setupTask <setupTask
@ -70,8 +69,6 @@
name="org.eclipse.jst.web_ui.feature.feature.group"/> name="org.eclipse.jst.web_ui.feature.feature.group"/>
<requirement <requirement
name="org.eclipse.m2e.feature.feature.group" versionRange="[1.14.0,2.0.0)"/> name="org.eclipse.m2e.feature.feature.group" versionRange="[1.14.0,2.0.0)"/>
<requirement
name="org.eclipse.oomph.setup.maven.feature.group"/>
<requirement <requirement
name="org.eclipse.oomph.setup.workingsets.feature.group"/> name="org.eclipse.oomph.setup.workingsets.feature.group"/>
<requirement <requirement
@ -99,15 +96,6 @@
source code for ${scope.project.label} source code for ${scope.project.label}
</description> </description>
</setupTask> </setupTask>
<setupTask
xsi:type="maven:MavenImportTask">
<sourceLocator
rootFolder="${checkout.location}"
locateNestedProjects="true">
<excludedPath>spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin</excludedPath>
</sourceLocator>
<description></description>
</setupTask>
<setupTask <setupTask
xsi:type="setup.workingsets:WorkingSetTask"> xsi:type="setup.workingsets:WorkingSetTask">
<workingSet <workingSet

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save