Compare commits

..

No commits in common. 'main' and '2.1.x' have entirely different histories.
main ... 2.1.x

@ -0,0 +1,40 @@
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-minor-version
prohibited:
- project: dom4j
versions:
# Old versions that use yyyymmdd format
- '[20040101,)'
- project: glassfish-jaxb
versions:
# Switches to Jarkarta EE API dependencies resulting in duplicate classes
- '[2.3.2,2.4)'
- project: saaj-impl
versions:
# Switches to Jarkarta EE API dependencies resulting in duplicate classes
- '[1.5.1,1.6)'
- project: selenium-htmlunit
versions:
# Requires a new minor of Selenium which contains breaking API changes
- '[2.33.1,2.34)'
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:
# Solr's Restlet dependencies
- 'https://maven.restlet.com'
# Caffeine Simulator's dependencies
- 'https://maven.imagej.net/content/repositories/public/'

@ -1,7 +0,0 @@
# .git-blame-ignore-revs
# Reformat code following spring-javaformat upgrade
df5898a1464112f185d295d585740de696934a12
c4de86c244acdcff69ed0aecacd254399be79ce2
b07269a018a4a9d4c029aba7dd8a15fa66df681c

@ -7,23 +7,19 @@ categories as some of them do not apply here.
STOP!! Please ask questions about how to use something, or to understand why something isn't
working as you expect it to, on Stack Overflow using the spring-boot tag.
- Security Vulnerability
STOP!! Please don't raise security vulnerabilities here. Head over to https://spring.io/security-policy to learn how to disclose them responsibly.
STOP!! Please don't raise security vulnerabilities here. Head over to https://pivotal.io/security to learn how to disclose them responsibly.
- Managed Dependency Upgrade
You DO NOT need to raise an issue for a managed dependency version upgrade as there's a semi-automatic process for checking managed dependencies for new versions before a release. BUT pull requests for upgrades that are more involved than just a version property change are still most welcome.
- With an Immediate Pull Request
An issue will be closed as a duplicate of the immediate pull request, so you don't have to raise an issue if you plan to create a pull request immediately.
🐞 Bug report (please don't include this emoji/text, just add your details)
🐞 Bug report (do not copy/paste)
Please provide details of the problem, including the version of Spring Boot that you
are using. If possible, please provide a test case or sample application that reproduces
the problem. This makes it much easier for us to diagnose the problem and to verify that
we have fixed it.
🎁 Enhancement (please don't include this emoji/text, just add your details)
🎁 Enhancement (do not copy/paste)
Please start by describing the problem that you are trying to solve. There may already
be a solution, or there may be a way to solve it that you hadn't considered.
TIP: You can always edit your issue if it isn't formatted correctly.
See https://guides.github.com/features/mastering-markdown
-->

@ -1,14 +1,11 @@
<!--
Thanks for contributing to Spring Boot. Please review the following notes before
submitting a pull request.
Please submit only genuine pull-requests. Do not use this repository as a GitHub
playground.
submitting you pull request.
Security Vulnerabilities
STOP! If your contribution fixes a security vulnerability, please do not submit it.
Instead, please head over to https://spring.io/security-policy to learn how to disclose a
Instead, please head over to https://pivotal.io/security to learn how to disclose a
vulnerability responsibly.
Dependency Upgrades

@ -1,14 +0,0 @@
name: Print JVM thread dumps
description: Prints a thread dump for all running JVMs
runs:
using: composite
steps:
- run: |
for java_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do
echo "------------------------ pid $java_pid ------------------------"
cat /proc/$java_pid/cmdline | xargs -0 echo
jcmd $java_pid Thread.print -l
jcmd $java_pid GC.heap_info
done
exit 0
shell: bash

@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

@ -1,43 +0,0 @@
name: Build Pull Request
on: pull_request
permissions:
contents: read
jobs:
build:
name: Build pull request
runs-on: ubuntu22-8-32
if: ${{ github.repository == 'spring-projects/spring-boot' }}
steps:
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'liberica'
- name: Check out code
uses: actions/checkout@v4
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Set up Gradle
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a
- name: Build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
- name: Print JVM thread dumps when cancelled
uses: ./.github/actions/print-jvm-thread-dumps
if: cancelled()
- name: Upload build reports
uses: actions/upload-artifact@v3
if: failure()
with:
name: build-reports
path: '**/build/reports/'

@ -1,13 +0,0 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
permissions:
contents: read
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v1

7
.gitignore vendored

@ -12,11 +12,13 @@
.classpath
.factorypath
.gradle
.idea
.metadata
.project
.recommenders
.settings
.springBeans
/build
.vscode
/code
MANIFEST.MF
@ -24,18 +26,17 @@ _site/
activemq-data
bin
build
!/**/src/**/bin
!/**/src/**/build
build.log
dependency-reduced-pom.xml
dump.rdb
interpolated*.xml
lib/
manifest.yml
out
overridedb.*
target
transaction-logs
.flattened-pom.xml
secrets.yml
.gradletasknamecache
.sts4-cache
.mvn/.gradle-enterprise/gradle-enterprise-workspace-id

10
.idea/.gitignore vendored

@ -1,10 +0,0 @@
.name
*.xml
/modules/
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -1,121 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="AUTODETECT_INDENTS" value="false" />
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="USE_TAB_CHARACTER" value="true" />
</value>
</option>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="500" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="500" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.springframework" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
<option name="RIGHT_MARGIN" value="90" />
<option name="ENABLE_JAVADOC_FORMATTING" value="false" />
<GroovyCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="500" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="500" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.springframework" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
</GroovyCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="CLASS_NAMES_IN_JAVADOC" value="3" />
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="500" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="500" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.springframework" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
<option name="ENABLE_JAVADOC_FORMATTING" value="false" />
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
<option name="JD_KEEP_INVALID_TAGS" value="false" />
<option name="JD_KEEP_EMPTY_LINES" value="false" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="false" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="20" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="20" />
</JetCodeStyleSettings>
<editorconfig>
<option name="ENABLED" value="false" />
</editorconfig>
<codeStyleSettings language="Groovy">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
<option name="BLANK_LINES_AROUND_FIELD" value="1" />
<option name="BLANK_LINES_AROUND_FIELD_IN_INTERFACE" value="1" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="true" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<indentOptions>
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

@ -1,6 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright 2012-&amp;#36;today.year the original author or authors.&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10; https://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
<option name="myName" value="java" />
</copyright>
</component>

@ -1,7 +0,0 @@
<component name="CopyrightManager">
<settings>
<module2copyright>
<element module="java" copyright="java" />
</module2copyright>
</settings>
</component>

@ -1,17 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="LombokGetterMayBeUsed" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NullableProblems" enabled="false" level="WARNING" enabled_by_default="false">
<option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE" value="true" />
<option name="REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_GETTER" value="true" />
<option name="REPORT_NOT_ANNOTATED_SETTER_PARAMETER" value="true" />
<option name="REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS" value="true" />
<option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" />
</inspection_tool>
<inspection_tool class="UnqualifiedFieldAccess" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
</profile>
</component>

@ -1,3 +0,0 @@
<component name="DependencyValidationManager">
<scope name="java" pattern="file:*.java&amp;&amp;!file:*package-info.java" />
</component>

@ -0,0 +1,12 @@
<extensions>
<extension>
<groupId>com.gradle</groupId>
<artifactId>gradle-enterprise-maven-extension</artifactId>
<version>1.6.6</version>
</extension>
<extension>
<groupId>io.spring.ge.conventions</groupId>
<artifactId>gradle-enterprise-conventions-maven-extension</artifactId>
<version>0.0.5</version>
</extension>
</extensions>

@ -0,0 +1,28 @@
<gradleEnterprise
xmlns="https://www.gradle.com/gradle-enterprise-maven" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.gradle.com/gradle-enterprise-maven https://www.gradle.com/schema/gradle-enterprise-maven.xsd">
<server>
<url>https://ge.spring.io</url>
</server>
<buildScan>
<publishIfAuthenticated>true</publishIfAuthenticated>
<obfuscation>
<ipAddresses>#{{'0.0.0.0'}}</ipAddresses>
</obfuscation>
</buildScan>
<buildCache>
<local>
<enabled>true</enabled>
</local>
<remote>
<enabled>true</enabled>
<storeEnabled><![CDATA[#{env['GRADLE_ENTERPRISE_CACHE_USERNAME'] != null && env['GRADLE_ENTERPRISE_CACHE_USERNAME'] != null}]]></storeEnabled>
<server>
<credentials>
<username>${env.GRADLE_ENTERPRISE_CACHE_USERNAME}</username>
<password>${env.GRADLE_ENTERPRISE_CACHE_PASSWORD}</password>
</credentials>
</server>
</remote>
</buildCache>
</gradleEnterprise>

@ -0,0 +1 @@
-Xmx2048m

Binary file not shown.

@ -0,0 +1,2 @@
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

@ -1,3 +0,0 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17.0.8.1-librca

@ -0,0 +1,160 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<profiles>
<profile>
<id>snapshot</id>
<repositories>
<repository>
<id>spring-ext</id>
<url>https://repo.spring.io/ext-release-local/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>jboss</id>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>rabbit-milestones</id>
<name>Rabbit Milestones</name>
<url>https://dl.bintray.com/rabbitmq/maven-milestones</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
<profile>
<id>milestone</id>
<repositories>
<repository>
<id>spring-ext</id>
<url>https://repo.spring.io/ext-release-local/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jboss</id>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>rabbit-milestones</id>
<name>Rabbit Milestones</name>
<url>https://dl.bintray.com/rabbitmq/maven-milestones</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
<profile>
<id>release</id>
<repositories>
<repository>
<id>spring-ext</id>
<url>https://repo.spring.io/ext-release-local/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jboss</id>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>@profile@</activeProfile>
</activeProfiles>
<servers>
<server>
<id>central</id>
<configuration>
<timeout>120000</timeout>
</configuration>
</server>
</servers>
</settings>

@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
contacting a project maintainer at code-of-conduct@spring.io. All complaints will
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
be reviewed and investigated and will result in a response that is deemed necessary and
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
with regard to the reporter of an incident.

@ -1,35 +1,45 @@
= Contributing to Spring Boot
Spring Boot is released under the Apache 2.0 license. If you would like to contribute something, or want to hack on the code this document should help you get started.
Spring Boot is released under the Apache 2.0 license. If you would like to contribute
something, or want to hack on the code this document should help you get started.
== Code of Conduct
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
By participating, you are expected to uphold this code. Please report unacceptable behavior to code-of-conduct@spring.io.
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of
conduct]. By participating, you are expected to uphold this code. Please report
unacceptable behavior to spring-code-of-conduct@pivotal.io.
== Using GitHub Issues
We use GitHub issues to track bugs and enhancements.
If you have a general usage question please ask on https://stackoverflow.com[Stack Overflow].
The Spring Boot team and the broader community monitor the https://stackoverflow.com/tags/spring-boot[`spring-boot`] tag.
We use GitHub issues to track bugs and enhancements. If you have a general usage question
please ask on https://stackoverflow.com[Stack Overflow]. The Spring Boot team and the
broader community monitor the https://stackoverflow.com/tags/spring-boot[`spring-boot`]
tag.
If you are reporting a bug, please help to speed up problem diagnosis by providing as much information as possible.
Ideally, that would include a small sample project that reproduces the problem.
If you are reporting a bug, please help to speed up problem diagnosis by providing as much
information as possible. Ideally, that would include a small
https://github.com/spring-projects/spring-boot-issues[sample project] that reproduces the
problem.
== Reporting Security Vulnerabilities
If you think you have found a security vulnerability in Spring Boot please *DO NOT* disclose it publicly until we've had a chance to fix it.
Please don't report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly.
If you think you have found a security vulnerability in Spring Boot please *DO NOT*
disclose it publicly until we've had a chance to fix it. Please don't report security
vulnerabilities using GitHub issues, instead head over to https://pivotal.io/security and
learn how to disclose them responsibly.
== Sign the Contributor License Agreement
Before we accept a non-trivial patch or pull request we will need you to https://cla.pivotal.io/sign/spring[sign the Contributor License Agreement].
Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do.
Active contributors might be asked to join the core team, and given the ability to merge pull requests.
Before we accept a non-trivial patch or pull request we will need you to
https://cla.pivotal.io/sign/spring[sign the Contributor License Agreement].
Signing the contributor's agreement does not grant anyone commit rights to the main
repository, but it does mean that we can accept your contributions, and you will get an
author credit if we do. Active contributors might be asked to join the core team, and
given the ability to merge pull requests.
@ -37,22 +47,178 @@ Active contributors might be asked to join the core team, and given the ability
None of these is essential for a pull request, but they will all help. They can also be
added after the original pull request but before a merge.
* We use the https://github.com/spring-io/spring-javaformat/[Spring JavaFormat] project to apply code formatting conventions.
If you use Eclipse and you follow the '`Importing into eclipse`' instructions below you should get project specific formatting automatically.
You can also install the https://github.com/spring-io/spring-javaformat/#intellij-idea[Spring JavaFormat IntelliJ Plugin] or format the code from the Gradle build by running `./gradlew format`.
Note that if you have format violations in `buildSrc`, you can fix them by running `./gradlew -p buildSrc format` from the project root directory.
* The build includes Checkstyle rules for many of our code conventions. Run `./gradlew checkstyleMain checkstyleTest` if you want to check your changes are compliant.
* Make sure all new `.java` files have a Javadoc class comment with at least an `@author` tag identifying you, and preferably at least a paragraph on what the class is for.
* Add the ASF license header comment to all new `.java` files (copy from existing files in the project).
* Add yourself as an `@author` to the `.java` files that you modify substantially (more than cosmetic changes).
* We use the https://github.com/spring-io/spring-javaformat/[Spring JavaFormat] project
to apply code formatting conventions. If you use Eclipse and you follow the '`Importing
into eclipse`' instructions below you should get project specific formatting
automatically. You can also install the https://github.com/spring-io/spring-javaformat/#intellij-idea[Spring JavaFormat IntelliJ Plugin]
or format the code from the Maven build by running
`./mvnw io.spring.javaformat:spring-javaformat-maven-plugin:apply`.
* The build includes checkstyle rules for many of our code conventions. Run
`./mvnw validate` if you want to check you changes are compliant.
* Make sure all new `.java` files have a Javadoc class comment with at least an
`@author` tag identifying you, and preferably at least a paragraph on what the class is
for.
* Add the ASF license header comment to all new `.java` files (copy from existing files
in the project)
* Add yourself as an `@author` to the `.java` files that you modify substantially (more
than cosmetic changes).
* Add some Javadocs.
* A few unit tests would help a lot as well -- someone has to do it.
* Verification tasks, including tests and Checkstyle, can be executed by running `./gradlew check` from the project root.
Note that `SPRING_PROFILES_ACTIVE` environment variable might affect the result of tests, so in that case, you can prevent it by running `unset SPRING_PROFILES_ACTIVE` before running the task.
* If no-one else is using your branch, please rebase it against the current main branch (or other target branch in the project).
* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions].
* If no-one else is using your branch, please rebase it against the current master (or
other target branch in the main project).
* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions],
if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit
message (where `XXXX` is the issue number).
== Working with the Code
For information on editing, building, and testing the code, see the https://github.com/spring-projects/spring-boot/wiki/Working-with-the-Code[Working with the Code] page on the project wiki.
If you don't have an IDE preference we would recommend that you use
https://spring.io/tools/sts[Spring Tools Suite] or
https://eclipse.org[Eclipse] when working with the code. We use the
https://eclipse.org/m2e/[M2Eclipse] eclipse plugin for maven support. Other IDEs and tools
should also work without issue.
=== Building from Source
Spring Boot source can be built from the command line using
https://maven.apache.org/run-maven/index.html[Apache Maven] on JDK 1.8 or above. We
include '`Maven Wrapper`' scripts (`./mvnw` or `mvnw.bat`) that you can run rather than
needing to install Maven locally.
==== 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]
----
$ ./mvnw -Pfull clean install
----
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 samples 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
You can import the Spring Boot code into any Eclipse 2019-12-based distribution. The
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).
==== Using the Eclipse Installer
Spring Boot includes a `.setup` files which can be used with the Eclipse Installer to
provision a new environment. To use the installer:
* Download and run the latest
https://www.eclipse.org/downloads/packages/installer[Eclipse Installer].
* Switch to "Advanced Mode" using the drop down menu on the right.
* Select "`Eclipse IDE for Java Developers`" under "`Eclipse.org`" as the product to
install, `2019-12` as the product version, and click "`next`".
* For the "`Project`" click on "`+`" to add a new setup file. Select "`Github Projects`"
and browse for `<checkout>/eclipse/spring-boot-project.setup` from your locally cloned
copy of the source code. Click "`OK`" to add the setup file to the list.
* Double-click on "`Spring Boot`" from the project list to add it to the list that will
be provisioned then click "`Next`".
* Click show all variables and make sure that "`Checkout Location`" points to the locally
cloned source code that you selected earlier. You might also want to pick a different
install location here.
* Click "`Finish`" to install the software.
Once complete you should find that a local workspace has been provisioned complete with
all required Eclipse plugins. Projects will be grouped into working-sets to make the code
easier to navigate.
If you want to work on the `spring-boot-gradle-plugin` you should remove the imported Maven
project and reimport it as a Gradle project.
TIP: If you see import errors with `com.sun` packages make sure you have setup a valid
`JavaSE-1.8` environment. From preferences select "`Java`", "`Installed JREs`",
"`Execution Environments`" and make sure "`JavaSE-1.8`" points to a Java 1.8
install (we use AdoptOpenJDK on our CI).
==== Manual Installation with M2Eclipse
If you prefer to install Eclipse yourself you should use the
https://eclipse.org/m2e/[M2Eclipse] eclipse plugin. If you don't already have m2eclipse
installed it is available from the "`Eclipse marketplace`".
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:
===== Install the Spring Formatter plugin
* Select "`Help`" -> "`Install New Software`".
* Add `https://dl.bintray.com/spring/javaformat-eclipse/` as a site.
* Install "Spring Java Format".
NOTE: The plugin is optional. Projects can be imported without the plugins, your code
changes just won't be automatically formatted.
With the requisite eclipse plugins installed you can select
`import existing maven projects` from the `file` menu to import the code. You will
need to import the root `spring-boot` pom and the `spring-boot-samples` pom separately.
=== Importing into Other IDEs
Maven is well supported by most Java IDEs. Refer to your vendor documentation.
== Integration Tests
The sample applications are used as integration tests during 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 sample directory.
== Cloning the git repository on Windows
Some files in the git repository may exceed the Windows maximum file path (260
characters), depending on where you clone the repository. If you get `Filename too long`
errors, set the `core.longPaths=true` git option:
```
git clone -c core.longPaths=true https://github.com/spring-projects/spring-boot
```

@ -4,7 +4,6 @@
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.

@ -1,25 +1,33 @@
= Spring Boot image:https://ci.spring.io/api/v1/teams/spring-boot/pipelines/spring-boot-3.2.x/jobs/build/badge["Build Status", link="https://ci.spring.io/teams/spring-boot/pipelines/spring-boot-3.2.x?groups=Build"] image:https://badges.gitter.im/Join Chat.svg["Chat",link="https://gitter.im/spring-projects/spring-boot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?&search.rootProjectNames=Spring%20Boot%20Build&search.rootProjectNames=spring-boot-build"]
= Spring Boot image:https://ci.spring.io/api/v1/teams/spring-boot/pipelines/spring-boot-2.1.x/jobs/build/badge["Build Status", link="https://ci.spring.io/teams/spring-boot/pipelines/spring-boot-2.1.x?groups=Build"] image:https://badges.gitter.im/Join Chat.svg["Chat",link="https://gitter.im/spring-projects/spring-boot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
:docs: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference
:github: https://github.com/spring-projects/spring-boot
Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
It takes an opinionated view of the Spring platform so that new and existing users can quickly get to the bits they need.
Spring Boot helps you to create Spring-powered, production-grade applications and
services with absolute minimum fuss. It takes an opinionated view of the Spring platform
so that new and existing users can quickly get to the bits they need.
You can use Spring Boot to create stand-alone Java applications that can be started using `java -jar` or more traditional WAR deployments.
We also provide a command-line tool that runs Spring scripts.
You can use Spring Boot to create stand-alone Java applications that can be started using
`java -jar` or more traditional WAR deployments. We also provide a command line tool
that runs spring scripts.
Our primary goals are:
* Provide a radically faster and widely accessible getting started experience for all Spring development.
* Be opinionated, but get out of the way quickly as requirements start to diverge from the defaults.
* Provide a range of non-functional features common to large classes of projects (for example, embedded servers, security, metrics, health checks, externalized configuration).
* Absolutely no code generation and no requirement for XML configuration.
* Provide a radically faster and widely accessible getting started experience for all
Spring development
* Be opinionated out of the box, but get out of the way quickly as requirements start to
diverge from the defaults
* Provide a range of non-functional features that are common to large classes of projects
(e.g. embedded servers, security, metrics, health checks, externalized configuration)
* Absolutely no code generation and no requirement for XML configuration
== Installation and Getting Started
The {docs}/html/[reference documentation] includes detailed {docs}/html/getting-started.html#getting-started-installing-spring-boot[installation instructions] as well as a comprehensive {docs}/html/getting-started.html#getting-started-first-application[``getting started``] guide.
The {docs}/htmlsingle/[reference documentation] includes detailed
{docs}/htmlsingle/#getting-started-installing-spring-boot[installation instructions]
as well as a comprehensive {docs}/htmlsingle/#getting-started-first-application[``getting
started``] guide. Documentation is published in {docs}/htmlsingle/[HTML],
{docs}/pdf/spring-boot-reference.pdf[PDF] and {docs}/epub/spring-boot-reference.epub[EPUB]
formats.
Here is a quick teaser of a complete Spring Boot application in Java:
@ -47,90 +55,158 @@ Here is a quick teaser of a complete Spring Boot application in Java:
== Getting Help
Are you having trouble with Spring Boot? We want to help!
== Getting help
Having trouble with Spring Boot? We'd like to help!
* Check the {docs}/html/[reference documentation], especially the {docs}/html/howto.html#howto[How-to's] -- they provide solutions to the most common questions.
* Learn the Spring basics -- Spring Boot builds on many other Spring projects; check the https://spring.io[spring.io] website for a wealth of reference documentation.
If you are new to Spring, try one of the https://spring.io/guides[guides].
* If you are upgrading, read the {github}/wiki[release notes] for upgrade instructions and "new and noteworthy" features.
* Ask a question -- we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-boot[`spring-boot`].
You can also chat with the community on https://gitter.im/spring-projects/spring-boot[Gitter].
* Report bugs with Spring Boot at {github}/issues[github.com/spring-projects/spring-boot/issues].
* Check the {docs}/htmlsingle/[reference documentation], especially the
{docs}/htmlsingle/#howto[How-to's] -- they provide solutions to the most common
questions.
* Learn the Spring basics -- Spring Boot builds on many other Spring projects, check
the https://spring.io[spring.io] web-site for a wealth of reference documentation. If
you are just starting out with Spring, try one of the https://spring.io/guides[guides].
* If you are upgrading, read the https://github.com/spring-projects/spring-boot/wiki[release notes]
for upgrade instructions and "new and noteworthy" features.
* Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions
tagged with https://stackoverflow.com/tags/spring-boot[`spring-boot`]. You can also chat
with the community on https://gitter.im/spring-projects/spring-boot[Gitter].
* Report bugs with Spring Boot at https://github.com/spring-projects/spring-boot/issues[github.com/spring-projects/spring-boot/issues].
== Reporting Issues
Spring Boot uses GitHub's integrated issue tracking system to record bugs and feature requests.
If you want to raise an issue, please follow the recommendations below:
Spring Boot uses GitHub's integrated issue tracking system to record bugs and feature
requests. If you want to raise an issue, please follow the recommendations below:
* Before you log a bug, please search the {github}/issues[issue tracker] to see if someone has already reported the problem.
* If the issue doesn't already exist, {github}/issues/new[create a new issue].
* Please provide as much information as possible with the issue report.
We like to know the Spring Boot version, operating system, and JVM version you're using.
* If you need to paste code or include a stack trace, use Markdown.
+++```+++ escapes before and after your text.
* If possible, try to create a test case or project that replicates the problem and attach it to the issue.
* Before you log a bug, please https://github.com/spring-projects/spring-boot/search?type=Issues[search the issue tracker]
to see if someone has already reported the problem.
* If the issue doesn't already exist, https://github.com/spring-projects/spring-boot/issues/new[create a new issue].
* Please provide as much information as possible with the issue report, we like to know
the version of Spring Boot that you are using, as well as your Operating System and
JVM version.
* If you need to paste code, or include a stack trace use Markdown +++```+++ escapes
before and after your text.
* If possible try to create a test-case or project that replicates the issue. You can
submit sample projects as pull-requests against the
https://github.com/spring-projects/spring-boot-issues[spring-boot-issues] GitHub
project. Use the issue number for the name of your project.
== Building from Source
You don't need to build from source to use Spring Boot (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Boot can be built and published to your local Maven cache using the https://docs.gradle.org/current/userguide/gradle_wrapper.html[Gradle wrapper].
You also need JDK 17.
You don't need to build from source to use Spring Boot (binaries in
https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and
greatest, Spring Boot can be easily built with the
https://github.com/takari/maven-wrapper[maven wrapper]. You also need JDK 1.8.
[indent=0]
----
$ ./mvnw clean install
----
If you want to build with the regular `mvn` command, you will need
https://maven.apache.org/run-maven/index.html[Maven v3.5.0 or above].
NOTE: You may need to increase the amount of memory available to Maven by setting
a `MAVEN_OPTS` environment variable with the value `-Xmx512m`. Remember
to set the corresponding property in your IDE as well if you are building and running
tests there (e.g. in Eclipse go to `Preferences->Java->Installed JREs` and edit the
JRE definition so that all processes are launched with those arguments). This property
is automatically set if you use the maven wrapper.
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests,
and in particular please fill out the
https://support.springsource.com/spring_committer_signup[Contributor's Agreement]
before your first change, however trivial._
=== Building reference documentation
First of all, make sure you have built the project:
[indent=0]
----
$ ./gradlew publishToMavenLocal
$ ./mvnw clean install
----
This will build all of the jars and documentation and publish them to your local Maven cache.
It won't run any of the tests.
If you want to build everything, use the `build` task:
The reference documentation requires the documentation of the Maven plugin to be
available so you need to build that first since it's not generated by default.
[indent=0]
----
$ ./gradlew build
$ ./mvnw clean install -pl spring-boot-project/spring-boot-tools/spring-boot-maven-plugin -Pdefault,full
----
The documentation also includes auto-generated information about the starters. You might
have that in your local repository already (per the first step) but if you want to refresh
it:
[indent=0]
----
$ ./mvnw clean install -f spring-boot-project/spring-boot-starters
----
Once this is done, you can build the reference documentation with the command below:
[indent=0]
----
$ ./mvnw clean prepare-package -pl spring-boot-project/spring-boot-docs -Pdefault,full
----
TIP: The generated documentation is available from `spring-boot-project/spring-boot-docs/target/contents/reference`
== Modules
There are several modules in Spring Boot. Here is a quick overview:
There are a number of modules in Spring Boot, here is a quick overview:
=== spring-boot
The main library providing features that support the other parts of Spring Boot. These include:
The main library providing features that support the other parts of Spring Boot,
these include:
* The `SpringApplication` class, providing static convenience methods that can be used to write a stand-alone Spring Application.
Its sole job is to create and refresh an appropriate Spring `ApplicationContext`.
* Embedded web applications with a choice of container (Tomcat, Jetty, or Undertow).
* First-class externalized configuration support.
* Convenience `ApplicationContext` initializers, including support for sensible logging defaults.
* The `SpringApplication` class, providing static convenience methods that can be used
to write a stand-alone Spring Application. Its sole job is to create and refresh an
appropriate Spring `ApplicationContext`
* Embedded web applications with a choice of container (Tomcat, Jetty or Undertow)
* First class externalized configuration support
* Convenience `ApplicationContext` initializers, including support for sensible logging
defaults
=== spring-boot-autoconfigure
Spring Boot can configure large parts of typical applications based on the content of their classpath.
A single `@EnableAutoConfiguration` annotation triggers auto-configuration of the Spring context.
Spring Boot can configure large parts of common applications based on the content
of their classpath. A single `@EnableAutoConfiguration` annotation triggers
auto-configuration of the Spring context.
Auto-configuration attempts to deduce which beans a user might need. For example, if `HSQLDB` is on the classpath, and the user has not configured any database connections, then they probably want an in-memory database to be defined.
Auto-configuration will always back away as the user starts to define their own beans.
Auto-configuration attempts to deduce which beans a user might need. For example, if
`HSQLDB` is on the classpath, and the user has not configured any database connections,
then they probably want an in-memory database to be defined. Auto-configuration will
always back away as the user starts to define their own beans.
=== spring-boot-starters
Starters are a set of convenient dependency descriptors that you can include in your application.
You get a one-stop shop for all the Spring and related technology you need without having to hunt through sample code and copy-paste loads of dependency descriptors.
For example, if you want to get started using Spring and JPA for database access, include the `spring-boot-starter-data-jpa` dependency in your project, and you are good to go.
Starters are a set of convenient dependency descriptors that you can include in
your application. You get a one-stop-shop for all the Spring and related technology
that you need without having to hunt through sample code and copy paste loads of
dependency descriptors. For example, if you want to get started using Spring and JPA for
database access include the `spring-boot-starter-data-jpa` dependency in your
project, and you are good to go.
=== spring-boot-cli
The Spring command line application compiles and runs Groovy source, allowing you to
write the absolute minimum of code to get an application running. Spring CLI
can also watch files, automatically recompiling and restarting when they change.
=== spring-boot-actuator
Actuator endpoints let you monitor and interact with your application.
Spring Boot Actuator provides the infrastructure required for actuator endpoints.
It contains annotation support for actuator endpoints.
This module provides many endpoints, including the `HealthEndpoint`, `EnvironmentEndpoint`, `BeansEndpoint`, and many more.
Spring Boot Actuator provides the infrastructure required for actuator endpoints. It contains
annotation support for actuator endpoints. Out of the box, this module provides a number of endpoints
including the `HealthEndpoint`, `EnvironmentEndpoint`, `BeansEndpoint` and many more.
@ -148,31 +224,53 @@ This module contains core items and annotations that can be helpful when testing
=== spring-boot-test-autoconfigure
Like other Spring Boot auto-configuration modules, spring-boot-test-autoconfigure provides auto-configuration for tests based on the classpath.
It includes many annotations that can automatically configure a slice of your application that needs to be tested.
Like other Spring Boot auto-configuration modules, spring-boot-test-autoconfigure, provides auto-configuration
for tests based on the classpath. It includes a number of annotations that can be used to automatically
configure a slice of your application that needs to be tested.
=== spring-boot-loader
Spring Boot Loader provides the secret sauce that allows you to build a single jar file that can be launched using `java -jar`.
Generally, you will not need to use `spring-boot-loader` directly but work with the link:spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin[Gradle] or link:spring-boot-project/spring-boot-tools/spring-boot-maven-plugin[Maven] plugin instead.
Spring Boot Loader provides the secret sauce that allows you to build a single jar file
that can be launched using `java -jar`. Generally you will not need to use
`spring-boot-loader` directly, but instead work with the
link:spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin[Gradle] or
link:spring-boot-project/spring-boot-tools/spring-boot-maven-plugin[Maven] plugin.
=== spring-boot-devtools
The spring-boot-devtools module provides additional development-time features, such as automatic restarts, for a smoother application development experience.
Developer tools are automatically disabled when running a fully packaged application.
The spring-boot-devtools module provides additional development-time features such as automatic restarts,
for a smoother application development experience. Developer tools are automatically disabled when
running a fully packaged application.
== Samples
Groovy samples for use with the command line application are available in
link:spring-boot-project/spring-boot-cli/samples[spring-boot-cli/samples]. To run the CLI samples type
`spring run <sample>.groovy` from samples directory.
Java samples are available in link:spring-boot-samples[spring-boot-samples] and should
be built with maven and run by invoking `java -jar target/<sample>.jar`.
== Guides
The https://spring.io/[spring.io] site contains several guides that show how to use Spring Boot step-by-step:
The https://spring.io/[spring.io] site contains several guides that show how to use Spring
Boot step-by-step:
* https://spring.io/guides/gs/spring-boot/[Building an Application with Spring Boot] is an introductory guide that shows you how to create an application, run it, and add some management services.
* https://spring.io/guides/gs/actuator-service/[Building a RESTful Web Service with Spring Boot Actuator] is a guide to creating a REST web service and also shows how the server can be configured.
* https://spring.io/guides/gs/convert-jar-to-war/[Converting a Spring Boot JAR Application to a WAR] shows you how to run applications in a web server as a WAR file.
* https://spring.io/guides/gs/spring-boot/[Building an Application with Spring Boot] is a
very basic guide that shows you how to create an application, run it and add some
management services.
* https://spring.io/guides/gs/actuator-service/[Building a RESTful Web Service with Spring
Boot Actuator] is a guide to creating a REST web service and also shows how the server
can be configured.
* https://spring.io/guides/gs/convert-jar-to-war/[Converting a Spring Boot JAR Application
to a WAR] shows you how to run applications in a web server as a WAR file.
== License
Spring Boot is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].
Spring Boot is Open Source software released under the
https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].

@ -1,19 +1,20 @@
= Getting support for Spring Boot
== GitHub issues
We choose not to use GitHub issues for general usage questions and support, preferring to
We choose not use GitHub issues for general usage questions and support, preferring to
use issues solely for the tracking of bugs and enhancements. If you have a general
usage question please do not open a GitHub issue, but use one of the other channels
described below.
If you are reporting a bug, please help to speed up problem diagnosis by providing as
much information as possible. Ideally, that would include a small sample project that
reproduces the problem.
much information as possible. Ideally, that would include a small
https://github.com/spring-projects/spring-boot-issues[sample project] that reproduces the
problem.
== Stack Overflow
The Spring Boot community monitors the
https://stackoverflow.com/tags/spring-boot[`spring-boot`] tag on Stack Overflow. Before
asking a question, please familiarize yourself with Stack Overflow's
asking a question, please familiar yourself with Stack Overflow's
https://stackoverflow.com/help/how-to-ask[advice on how to ask a good question].
== Gitter
@ -21,6 +22,6 @@ If you want to discuss something or have a question that isn't suited to Stack O
the Spring Boot community chat in the
https://gitter.im/spring-projects/spring-boot[#spring-boot room on Gitter].
== VMware Open Source Software Support
If you are interested in more dedicated support, VMware provides
https://spring.io/support[premium support] for Spring Boot.
== Pivotal Open Source Software Support
If you are interested in more dedicated support, Pivotal provides
https://pivotal.io/support/oss[premium support] for Spring Boot.

@ -1,49 +0,0 @@
plugins {
id "base"
id "org.jetbrains.kotlin.jvm" apply false // https://youtrack.jetbrains.com/issue/KT-30276
id "io.spring.nohttp" version "0.0.11"
}
description = "Spring Boot Build"
defaultTasks 'build'
nohttp {
allowlistFile = project.file("src/nohttp/allowlist.lines")
source.exclude "**/bin/**"
source.exclude "**/build/**"
source.exclude "**/out/**"
source.exclude "**/target/**"
source.exclude "**/.settings/**"
source.exclude "**/.classpath"
source.exclude "**/.project"
source.exclude "spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/export.tar"
}
check {
dependsOn checkstyleNohttp
}
allprojects {
group "org.springframework.boot"
repositories {
mavenCentral()
if (version.contains('-')) {
maven { url "https://repo.spring.io/milestone" }
}
if (version.endsWith('-SNAPSHOT')) {
maven { url "https://repo.spring.io/snapshot" }
}
}
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, "minutes"
}
}
tasks.named("checkstyleNohttp").configure {
maxHeapSize = "1536m"
}

@ -1,139 +0,0 @@
plugins {
id "java-gradle-plugin"
id "io.spring.javaformat" version "${javaFormatVersion}"
id "checkstyle"
id "eclipse"
}
repositories {
mavenCentral()
gradlePluginPortal()
}
sourceCompatibility = 17
targetCompatibility = 17
def versions = [:]
new File(projectDir.parentFile, "gradle.properties").withInputStream {
def properties = new Properties()
properties.load(it)
["assertj", "commonsCodec", "hamcrest", "jackson", "junitJupiter",
"kotlin", "maven"].each {
versions[it] = properties[it + "Version"]
}
}
versions["springFramework"] = "6.0.12"
ext.set("versions", versions)
if (versions.springFramework.contains("-")) {
repositories {
maven { url "https://repo.spring.io/milestone" }
maven { url "https://repo.spring.io/snapshot" }
}
}
dependencies {
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
implementation(platform("org.springframework:spring-framework-bom:${versions.springFramework}"))
implementation("com.diffplug.gradle:goomph:3.37.2")
implementation("com.fasterxml.jackson.core:jackson-databind:${versions.jackson}")
implementation("com.gradle:gradle-enterprise-gradle-plugin:3.12.1")
implementation("com.tngtech.archunit:archunit:1.0.0")
implementation("commons-codec:commons-codec:${versions.commonsCodec}")
implementation("de.undercouch.download:de.undercouch.download.gradle.plugin:5.5.0")
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}")
implementation("org.apache.maven:maven-embedder:${versions.maven}")
implementation("org.asciidoctor:asciidoctor-gradle-jvm:3.3.2")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}")
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${versions.kotlin}")
implementation("org.springframework:spring-context")
implementation("org.springframework:spring-core")
implementation("org.springframework:spring-web")
testImplementation("org.assertj:assertj-core:${versions.assertj}")
testImplementation("org.hamcrest:hamcrest:${versions.hamcrest}")
testImplementation("org.junit.jupiter:junit-jupiter:${versions.junitJupiter}")
testImplementation("org.springframework:spring-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
checkstyle {
toolVersion = "10.12.4"
}
gradlePlugin {
plugins {
annotationProcessorPlugin {
id = "org.springframework.boot.annotation-processor"
implementationClass = "org.springframework.boot.build.processors.AnnotationProcessorPlugin"
}
architecturePlugin {
id = "org.springframework.boot.architecture"
implementationClass = "org.springframework.boot.build.architecture.ArchitecturePlugin"
}
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"
}
systemTestPlugin {
id = "org.springframework.boot.system-test"
implementationClass = "org.springframework.boot.build.test.SystemTestPlugin"
}
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"
}
processedAnnotationsPlugin {
id = "org.springframework.boot.processed-annotations"
implementationClass = "org.springframework.boot.build.processors.ProcessedAnnotationsPlugin"
}
starterPlugin {
id = "org.springframework.boot.starter"
implementationClass = "org.springframework.boot.build.starters.StarterPlugin"
}
testFailuresPlugin {
id = "org.springframework.boot.test-failures"
implementationClass = "org.springframework.boot.build.testing.TestFailuresPlugin"
}
}
}
test {
useJUnitPlatform()
}
eclipse.classpath.file.whenMerged {
def jreEntry = entries.find { it.path.contains("org.eclipse.jdt.launching.JRE_CONTAINER") }
jreEntry.entryAttributes['module'] = 'true'
jreEntry.entryAttributes['limit-modules'] = 'java.base'
}

@ -1,9 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<module name="io.spring.javaformat.checkstyle.SpringChecks">
<property name="excludes" value="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck" />
</module>
</module>

@ -1 +0,0 @@
javaFormatVersion=0.0.38

@ -1,6 +0,0 @@
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}

@ -1,159 +0,0 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask;
import org.asciidoctor.gradle.jvm.AsciidoctorJExtension;
import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin;
import org.asciidoctor.gradle.jvm.AsciidoctorTask;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.Sync;
import org.springframework.boot.build.artifacts.ArtifactRelease;
import org.springframework.util.StringUtils;
/**
* Conventions that are applied in the presence of the {@link AsciidoctorJPlugin}. When
* the plugin is applied:
*
* <ul>
* <li>All warnings are made fatal.
* <li>The version of AsciidoctorJ is upgraded to 2.4.3.
* <li>An {@code asciidoctorExtensions} configuration is created.
* <li>For each {@link AsciidoctorTask} (HTML only):
* <ul>
* <li>A task is created to sync the documentation resources to its output directory.
* <li>{@code doctype} {@link AsciidoctorTask#options(Map) option} is configured.
* <li>The {@code backend} is configured.
* </ul>
* <li>For each {@link AbstractAsciidoctorTask} (HTML and PDF):
* <ul>
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured to enable
* warnings for references to missing attributes, the GitHub tag, the Artifactory repo for
* the current version, etc.
* <li>{@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()}
* is enabled.
* <li>{@code asciidoctorExtensions} is added to the task's configurations.
* </ul>
* </ul>
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
class AsciidoctorConventions {
private static final String ASCIIDOCTORJ_VERSION = "2.4.3";
private static final String EXTENSIONS_CONFIGURATION_NAME = "asciidoctorExtensions";
void apply(Project project) {
project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> {
makeAllWarningsFatal(project);
upgradeAsciidoctorJVersion(project);
createAsciidoctorExtensionsConfiguration(project);
project.getTasks()
.withType(AbstractAsciidoctorTask.class,
(asciidoctorTask) -> configureAsciidoctorTask(project, asciidoctorTask));
});
}
private void makeAllWarningsFatal(Project project) {
project.getExtensions().getByType(AsciidoctorJExtension.class).fatalWarnings(".*");
}
private void upgradeAsciidoctorJVersion(Project project) {
project.getExtensions().getByType(AsciidoctorJExtension.class).setVersion(ASCIIDOCTORJ_VERSION);
}
private void createAsciidoctorExtensionsConfiguration(Project project) {
project.getConfigurations().create(EXTENSIONS_CONFIGURATION_NAME, (configuration) -> {
project.getConfigurations()
.matching((candidate) -> "dependencyManagement".equals(candidate.getName()))
.all(configuration::extendsFrom);
configuration.getDependencies()
.add(project.getDependencies()
.create("io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.5"));
configuration.getDependencies()
.add(project.getDependencies().create("org.asciidoctor:asciidoctorj-pdf:1.5.3"));
});
}
private void configureAsciidoctorTask(Project project, AbstractAsciidoctorTask asciidoctorTask) {
asciidoctorTask.configurations(EXTENSIONS_CONFIGURATION_NAME);
configureCommonAttributes(project, asciidoctorTask);
configureOptions(asciidoctorTask);
configureForkOptions(asciidoctorTask);
asciidoctorTask.baseDirFollowsSourceDir();
createSyncDocumentationSourceTask(project, asciidoctorTask);
if (asciidoctorTask instanceof AsciidoctorTask task) {
boolean pdf = task.getName().toLowerCase().contains("pdf");
String backend = (!pdf) ? "spring-html" : "spring-pdf";
task.outputOptions((outputOptions) -> outputOptions.backends(backend));
}
}
private void configureCommonAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) {
ArtifactRelease artifacts = ArtifactRelease.forProject(project);
Map<String, Object> attributes = new HashMap<>();
attributes.put("attribute-missing", "warn");
attributes.put("github-tag", determineGitHubTag(project));
attributes.put("artifact-release-type", artifacts.getType());
attributes.put("artifact-download-repo", artifacts.getDownloadRepo());
attributes.put("revnumber", null);
asciidoctorTask.attributes(attributes);
}
// See https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/597
private void configureForkOptions(AbstractAsciidoctorTask asciidoctorTask) {
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) {
asciidoctorTask.forkOptions((options) -> options.jvmArgs("--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED",
"--add-opens", "java.base/java.io=ALL-UNNAMED"));
}
}
private String determineGitHubTag(Project project) {
String version = "v" + project.getVersion();
return (version.endsWith("-SNAPSHOT")) ? "main" : version;
}
private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) {
asciidoctorTask.options(Collections.singletonMap("doctype", "book"));
}
private Sync createSyncDocumentationSourceTask(Project project, AbstractAsciidoctorTask asciidoctorTask) {
Sync syncDocumentationSource = project.getTasks()
.create("syncDocumentationSourceFor" + StringUtils.capitalize(asciidoctorTask.getName()), Sync.class);
File syncedSource = new File(project.getBuildDir(), "docs/src/" + asciidoctorTask.getName());
syncDocumentationSource.setDestinationDir(syncedSource);
syncDocumentationSource.from("src/docs/");
asciidoctorTask.dependsOn(syncDocumentationSource);
asciidoctorTask.getInputs()
.dir(syncedSource)
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("synced source");
asciidoctorTask.setSourceDir(project.relativePath(new File(syncedSource, "asciidoc/")));
return syncDocumentationSource;
}
}

@ -1,54 +0,0 @@
/*
* Copyright 2012-2023 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.asciidoctor.gradle.jvm.AsciidoctorJPlugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
/**
* Plugin to apply conventions to projects that are part of Spring Boot's build.
* Conventions are applied in response to various plugins being applied.
*
* When the {@link JavaBasePlugin} is applied, the conventions in {@link JavaConventions}
* are applied.
*
* When the {@link MavenPublishPlugin} is applied, the conventions in
* {@link MavenPublishingConventions} are applied.
*
* When the {@link AsciidoctorJPlugin} is applied, the conventions in
* {@link AsciidoctorConventions} are applied.
*
* @author Andy Wilkinson
* @author Christoph Dreis
* @author Mike Smithson
*/
public class ConventionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
new JavaConventions().apply(project);
new MavenPublishingConventions().apply(project);
new AsciidoctorConventions().apply(project);
new KotlinConventions().apply(project);
new WarConventions().apply(project);
new EclipseConventions().apply(project);
}
}

@ -1,61 +0,0 @@
/*
* Copyright 2012-2023 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;
import org.gradle.api.tasks.bundling.Jar;
/**
* 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
@SuppressWarnings("deprecation")
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.afterEvaluate((evaluated) -> project.getPlugins().withType(JavaPlugin.class).all((javaPlugin) -> {
if (((Jar) project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME)).isEnabled()) {
project.getComponents()
.matching((component) -> component.getName().equals("java"))
.all(mavenPublication::from);
}
}));
project.getPlugins()
.withType(JavaPlatformPlugin.class)
.all((javaPlugin) -> project.getComponents()
.matching((component) -> component.getName().equals("javaPlatform"))
.all(mavenPublication::from));
}
}

@ -1,69 +0,0 @@
/*
* Copyright 2023-2023 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.Project;
import org.gradle.plugins.ide.api.XmlFileContentMerger;
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
import org.gradle.plugins.ide.eclipse.model.Classpath;
import org.gradle.plugins.ide.eclipse.model.ClasspathEntry;
import org.gradle.plugins.ide.eclipse.model.EclipseClasspath;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
import org.gradle.plugins.ide.eclipse.model.Library;
/**
* Conventions that are applied in the presence of the {@link EclipsePlugin} to work
* around buildship issue {@code #1238}.
*
* @author Phillip Webb
*/
class EclipseConventions {
void apply(Project project) {
project.getPlugins().withType(EclipsePlugin.class, (eclipse) -> {
EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class);
eclipseModel.classpath(this::configureClasspath);
});
}
private void configureClasspath(EclipseClasspath classpath) {
classpath.file(this::configureClasspathFile);
}
private void configureClasspathFile(XmlFileContentMerger merger) {
merger.whenMerged((content) -> {
if (content instanceof Classpath classpath) {
classpath.getEntries().removeIf(this::isKotlinPluginContributedBuildDirectory);
}
});
}
private boolean isKotlinPluginContributedBuildDirectory(ClasspathEntry entry) {
return (entry instanceof Library library) && isKotlinPluginContributedBuildDirectory(library.getPath())
&& isTest(library);
}
private boolean isKotlinPluginContributedBuildDirectory(String path) {
return path.contains("/main") && (path.contains("/build/classes/") || path.contains("/build/resources/"));
}
private boolean isTest(Library library) {
Object value = library.getEntryAttributes().get("test");
return (value instanceof String string && Boolean.parseBoolean(string));
}
}

@ -1,96 +0,0 @@
/*
* Copyright 2012-2022 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.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Task;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.PropertyPlaceholderHelper;
/**
* {@link Task} to extract resources from the classpath and write them to disk.
*
* @author Andy Wilkinson
*/
public class ExtractResources extends DefaultTask {
private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}");
private final Map<String, String> properties = new HashMap<>();
private final DirectoryProperty destinationDirectory;
private List<String> resourceNames = new ArrayList<>();
public ExtractResources() {
this.destinationDirectory = getProject().getObjects().directoryProperty();
}
@Input
public List<String> getResourceNames() {
return this.resourceNames;
}
public void setResourcesNames(List<String> resourceNames) {
this.resourceNames = resourceNames;
}
@OutputDirectory
public DirectoryProperty getDestinationDirectory() {
return this.destinationDirectory;
}
public void property(String name, String value) {
this.properties.put(name, value);
}
@Input
public Map<String, String> getProperties() {
return this.properties;
}
@TaskAction
void extractResources() throws IOException {
for (String resourceName : this.resourceNames) {
InputStream resourceStream = getClass().getClassLoader().getResourceAsStream(resourceName);
if (resourceStream == null) {
throw new GradleException("Resource '" + resourceName + "' does not exist");
}
String resource = FileCopyUtils.copyToString(new InputStreamReader(resourceStream, StandardCharsets.UTF_8));
resource = this.propertyPlaceholderHelper.replacePlaceholders(resource, this.properties::get);
FileCopyUtils.copy(resource,
new FileWriter(this.destinationDirectory.file(resourceName).get().getAsFile()));
}
}
}

@ -1,298 +0,0 @@
/*
* Copyright 2012-2023 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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import com.gradle.enterprise.gradleplugin.testretry.TestRetryExtension;
import com.gradle.enterprise.gradleplugin.testselection.PredictiveTestSelectionExtension;
import io.spring.javaformat.gradle.SpringJavaFormatPlugin;
import io.spring.javaformat.gradle.tasks.CheckFormat;
import io.spring.javaformat.gradle.tasks.Format;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.quality.Checkstyle;
import org.gradle.api.plugins.quality.CheckstyleExtension;
import org.gradle.api.plugins.quality.CheckstylePlugin;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
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;
import org.gradle.external.javadoc.CoreJavadocOptions;
import org.springframework.boot.build.architecture.ArchitecturePlugin;
import org.springframework.boot.build.classpath.CheckClasspathForProhibitedDependencies;
import org.springframework.boot.build.optional.OptionalDependenciesPlugin;
import org.springframework.boot.build.testing.TestFailuresPlugin;
import org.springframework.boot.build.toolchain.ToolchainPlugin;
import org.springframework.util.StringUtils;
/**
* Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the
* plugin is applied:
*
* <ul>
* <li>The project is configured with source and target compatibility of 17
* <li>{@link SpringJavaFormatPlugin Spring Java Format}, {@link CheckstylePlugin
* Checkstyle}, {@link TestFailuresPlugin Test Failures}, and {@link ArchitecturePlugin
* Architecture} plugins are applied
* <li>{@link Test} tasks are configured:
* <ul>
* <li>to use JUnit Platform
* <li>with a max heap of 1024M
* <li>to run after any Checkstyle and format checking tasks
* <li>to enable retries with a maximum of three attempts when running on CI
* <li>to use predictive test selection when the value of the
* {@code ENABLE_PREDICTIVE_TEST_SELECTION} environment variable is {@code true}
* </ul>
* <li>A {@code testRuntimeOnly} dependency upon
* {@code org.junit.platform:junit-platform-launcher} is added to projects with the
* {@link JavaPlugin} applied
* <li>{@link JavaCompile}, {@link Javadoc}, and {@link Format} tasks are configured to
* use UTF-8 encoding
* <li>{@link JavaCompile} tasks are configured to:
* <ul>
* <li>Use {@code -parameters}.
* <li>Treat warnings as errors
* <li>Enable {@code unchecked}, {@code deprecation}, {@code rawtypes}, and {@code varags}
* warnings
* </ul>
* <li>{@link Jar} tasks are configured to produce jars with LICENSE.txt and NOTICE.txt
* files and 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>
* <li>{@code spring-boot-parent} is used for dependency management</li>
* </ul>
*
* <p/>
*
* @author Andy Wilkinson
* @author Christoph Dreis
* @author Mike Smithson
* @author Scott Frederick
*/
class JavaConventions {
private static final String SOURCE_AND_TARGET_COMPATIBILITY = "17";
void apply(Project project) {
project.getPlugins().withType(JavaBasePlugin.class, (java) -> {
project.getPlugins().apply(TestFailuresPlugin.class);
project.getPlugins().apply(ArchitecturePlugin.class);
configureSpringJavaFormat(project);
configureJavaConventions(project);
configureJavadocConventions(project);
configureTestConventions(project);
configureJarManifestConventions(project);
configureDependencyManagement(project);
configureToolchain(project);
configureProhibitedDependencyChecks(project);
});
}
private void configureJarManifestConventions(Project project) {
ExtractResources extractLegalResources = project.getTasks()
.create("extractLegalResources", ExtractResources.class);
extractLegalResources.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("legal"));
extractLegalResources.setResourcesNames(Arrays.asList("LICENSE.txt", "NOTICE.txt"));
extractLegalResources.property("version", project.getVersion().toString());
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
Set<String> sourceJarTaskNames = sourceSets.stream()
.map(SourceSet::getSourcesJarTaskName)
.collect(Collectors.toSet());
Set<String> javadocJarTaskNames = sourceSets.stream()
.map(SourceSet::getJavadocJarTaskName)
.collect(Collectors.toSet());
project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> {
jar.metaInf((metaInf) -> metaInf.from(extractLegalResources));
jar.manifest((manifest) -> {
Map<String, Object> attributes = new TreeMap<>();
attributes.put("Automatic-Module-Name", project.getName().replace("-", "."));
attributes.put("Build-Jdk-Spec", SOURCE_AND_TARGET_COMPATIBILITY);
attributes.put("Built-By", "Spring");
attributes.put("Implementation-Title",
determineImplementationTitle(project, sourceJarTaskNames, javadocJarTaskNames, jar));
attributes.put("Implementation-Version", project.getVersion());
manifest.attributes(attributes);
});
}));
}
private String determineImplementationTitle(Project project, Set<String> sourceJarTaskNames,
Set<String> javadocJarTaskNames, Jar jar) {
if (sourceJarTaskNames.contains(jar.getName())) {
return "Source for " + project.getName();
}
if (javadocJarTaskNames.contains(jar.getName())) {
return "Javadoc for " + project.getName();
}
return project.getDescription();
}
private void configureTestConventions(Project project) {
project.getTasks().withType(Test.class, (test) -> {
test.useJUnitPlatform();
test.setMaxHeapSize("1024M");
project.getTasks().withType(Checkstyle.class, test::mustRunAfter);
project.getTasks().withType(CheckFormat.class, test::mustRunAfter);
configureTestRetries(test);
configurePredictiveTestSelection(test);
});
project.getPlugins()
.withType(JavaPlugin.class, (javaPlugin) -> project.getDependencies()
.add(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME, "org.junit.platform:junit-platform-launcher"));
}
private void configureTestRetries(Test test) {
TestRetryExtension testRetry = test.getExtensions().getByType(TestRetryExtension.class);
testRetry.getFailOnPassedAfterRetry().set(false);
testRetry.getMaxRetries().set(isCi() ? 3 : 0);
}
private boolean isCi() {
return Boolean.parseBoolean(System.getenv("CI"));
}
private void configurePredictiveTestSelection(Test test) {
if (isPredictiveTestSelectionEnabled()) {
PredictiveTestSelectionExtension predictiveTestSelection = test.getExtensions()
.getByType(PredictiveTestSelectionExtension.class);
predictiveTestSelection.getEnabled().convention(true);
}
}
private boolean isPredictiveTestSelectionEnabled() {
return Boolean.parseBoolean(System.getenv("ENABLE_PREDICTIVE_TEST_SELECTION"));
}
private void configureJavadocConventions(Project project) {
project.getTasks().withType(Javadoc.class, (javadoc) -> {
CoreJavadocOptions options = (CoreJavadocOptions) javadoc.getOptions();
options.source("17");
options.encoding("UTF-8");
options.addStringOption("Xdoclint:none", "-quiet");
});
}
private void configureJavaConventions(Project project) {
if (!project.hasProperty("toolchainVersion")) {
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
javaPluginExtension.setSourceCompatibility(JavaVersion.toVersion(SOURCE_AND_TARGET_COMPATIBILITY));
}
project.getTasks().withType(JavaCompile.class, (compile) -> {
compile.getOptions().setEncoding("UTF-8");
List<String> args = compile.getOptions().getCompilerArgs();
if (!args.contains("-parameters")) {
args.add("-parameters");
}
if (project.hasProperty("toolchainVersion")) {
compile.setSourceCompatibility(SOURCE_AND_TARGET_COMPATIBILITY);
compile.setTargetCompatibility(SOURCE_AND_TARGET_COMPATIBILITY);
}
else if (buildingWithJava17(project)) {
args.addAll(Arrays.asList("-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes",
"-Xlint:varargs"));
}
});
}
private boolean buildingWithJava17(Project project) {
return !project.hasProperty("toolchainVersion") && JavaVersion.current() == JavaVersion.VERSION_17;
}
private void configureSpringJavaFormat(Project project) {
project.getPlugins().apply(SpringJavaFormatPlugin.class);
project.getTasks().withType(Format.class, (Format) -> Format.setEncoding("UTF-8"));
project.getPlugins().apply(CheckstylePlugin.class);
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("8.45.1");
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));
}
private void configureDependencyManagement(Project project) {
ConfigurationContainer configurations = project.getConfigurations();
Configuration dependencyManagement = configurations.create("dependencyManagement", (configuration) -> {
configuration.setVisible(false);
configuration.setCanBeConsumed(false);
configuration.setCanBeResolved(false);
});
configurations
.matching((configuration) -> configuration.getName().endsWith("Classpath")
|| JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME.equals(configuration.getName()))
.all((configuration) -> configuration.extendsFrom(dependencyManagement));
Dependency springBootParent = project.getDependencies()
.enforcedPlatform(project.getDependencies()
.project(Collections.singletonMap("path", ":spring-boot-project:spring-boot-parent")));
dependencyManagement.getDependencies().add(springBootParent);
project.getPlugins()
.withType(OptionalDependenciesPlugin.class,
(optionalDependencies) -> configurations
.getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME)
.extendsFrom(dependencyManagement));
}
private void configureToolchain(Project project) {
project.getPlugins().apply(ToolchainPlugin.class);
}
private void configureProhibitedDependencyChecks(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project,
sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName()));
}
private void createProhibitedDependenciesChecks(Project project, String... configurationNames) {
ConfigurationContainer configurations = project.getConfigurations();
for (String configurationName : configurationNames) {
Configuration configuration = configurations.getByName(configurationName);
createProhibitedDependenciesCheck(configuration, project);
}
}
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);
}
}

@ -1,63 +0,0 @@
/*
* Copyright 2012-2023 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.ArrayList;
import java.util.List;
import org.gradle.api.Project;
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
/**
* Conventions that are applied in the presence of the {@code org.jetbrains.kotlin.jvm}
* plugin. When the plugin is applied:
*
* <ul>
* <li>{@link KotlinCompile} tasks are configured to:
* <ul>
* <li>Use {@code apiVersion} and {@code languageVersion} 1.7.
* <li>Use {@code jvmTarget} 17.
* <li>Treat all warnings as errors
* <li>Suppress version warnings
* </ul>
* </ul>
*
* <p/>
*
* @author Andy Wilkinson
*/
class KotlinConventions {
void apply(Project project) {
project.getPlugins()
.withId("org.jetbrains.kotlin.jvm",
(plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure));
}
private void configure(KotlinCompile compile) {
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
kotlinOptions.setApiVersion("1.7");
kotlinOptions.setLanguageVersion("1.7");
kotlinOptions.setJvmTarget("17");
kotlinOptions.setAllWarningsAsErrors(true);
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
freeCompilerArgs.add("-Xsuppress-version-warnings");
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
}
}

@ -1,172 +0,0 @@
/*
* Copyright 2012-2023 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.apache.maven.artifact.repository.MavenArtifactRepository;
import org.gradle.api.Project;
import org.gradle.api.attributes.Usage;
import org.gradle.api.component.AdhocComponentWithVariants;
import org.gradle.api.component.ConfigurationVariantDetails;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.VariantVersionMappingStrategy;
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;
/**
* Conventions that are applied in the presence of the {@link MavenPublishPlugin}. When
* the 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:
* <ul>
* <li>Creation of Javadoc and source jars is enabled.
* <li>Publication metadata (poms and Gradle module metadata) is configured to use
* resolved versions.
* </ul>
* </ul>
*
* @author Andy Wilkinson
* @author Christoph Dreis
* @author Mike Smithson
*/
class MavenPublishingConventions {
void apply(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) -> customizeMavenPublication(mavenPublication, project));
project.getPlugins().withType(JavaPlugin.class).all((javaPlugin) -> {
JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class);
extension.withJavadocJar();
extension.withSourcesJar();
});
});
}
private void customizeMavenPublication(MavenPublication publication, Project project) {
customizePom(publication.getPom(), project);
project.getPlugins()
.withType(JavaPlugin.class)
.all((javaPlugin) -> customizeJavaMavenPublication(publication, project));
suppressMavenOptionalFeatureWarnings(publication);
}
private void customizePom(MavenPom pom, Project project) {
pom.getUrl().set("https://spring.io/projects/spring-boot");
pom.getName().set(project.provider(project::getName));
pom.getDescription().set(project.provider(project::getDescription));
if (!isUserInherited(project)) {
pom.organization(this::customizeOrganization);
}
pom.licenses(this::customizeLicences);
pom.developers(this::customizeDevelopers);
pom.scm((scm) -> customizeScm(scm, project));
if (!isUserInherited(project)) {
pom.issueManagement(this::customizeIssueManagement);
}
}
private void customizeJavaMavenPublication(MavenPublication publication, Project project) {
addMavenOptionalFeature(project);
publication.versionMapping((strategy) -> strategy.usage(Usage.JAVA_API, (mappingStrategy) -> mappingStrategy
.fromResolutionOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)));
publication.versionMapping(
(strategy) -> strategy.usage(Usage.JAVA_RUNTIME, VariantVersionMappingStrategy::fromResolutionResult));
}
/**
* Add a feature that allows maven plugins to declare optional dependencies that
* appear in the POM. This is required to make m2e in Eclipse happy.
* @param project the project to add the feature to
*/
private void addMavenOptionalFeature(Project project) {
JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class);
extension.registerFeature("mavenOptional",
(feature) -> feature.usingSourceSet(extension.getSourceSets().getByName("main")));
AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.getComponents()
.findByName("java");
javaComponent.addVariantsFromConfiguration(
project.getConfigurations().findByName("mavenOptionalRuntimeElements"),
ConfigurationVariantDetails::mapToOptional);
}
private void suppressMavenOptionalFeatureWarnings(MavenPublication publication) {
publication.suppressPomMetadataWarningsFor("mavenOptionalApiElements");
publication.suppressPomMetadataWarningsFor("mavenOptionalRuntimeElements");
}
private void customizeOrganization(MavenPomOrganization organization) {
organization.getName().set("VMware, 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("https://www.apache.org/licenses/LICENSE-2.0");
});
}
private void customizeDevelopers(MavenPomDeveloperSpec developers) {
developers.developer((developer) -> {
developer.getName().set("Spring");
developer.getEmail().set("ask@spring.io");
developer.getOrganization().set("VMware, Inc.");
developer.getOrganizationUrl().set("https://www.spring.io");
});
}
private void customizeScm(MavenPomScm scm, Project project) {
if (!isUserInherited(project)) {
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");
}
private boolean isUserInherited(Project project) {
return "spring-boot-starter-parent".equals(project.getName())
|| "spring-boot-dependencies".equals(project.getName());
}
}

@ -1,120 +0,0 @@
/*
* Copyright 2012-2023 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);
}
}
}

@ -1,75 +0,0 @@
/*
* Copyright 2021-2023 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.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
/**
* Tasks for syncing the source code of a Spring Boot application, filtering its
* {@code build.gradle} to set the version of its {@code org.springframework.boot} plugin.
*
* @author Andy Wilkinson
*/
public class SyncAppSource extends DefaultTask {
private final DirectoryProperty sourceDirectory;
private final DirectoryProperty destinationDirectory;
private final Property<String> pluginVersion;
public SyncAppSource() {
ObjectFactory objects = getProject().getObjects();
this.sourceDirectory = objects.directoryProperty();
this.destinationDirectory = objects.directoryProperty();
this.pluginVersion = objects.property(String.class)
.convention(getProject().provider(() -> getProject().getVersion().toString()));
}
@TaskAction
void syncAppSources() {
getProject().sync((copySpec) -> {
copySpec.from(this.sourceDirectory);
copySpec.into(this.destinationDirectory);
copySpec.filter((line) -> line.replace("id \"org.springframework.boot\"",
"id \"org.springframework.boot\" version \"" + getProject().getVersion() + "\""));
});
}
@InputDirectory
public DirectoryProperty getSourceDirectory() {
return this.sourceDirectory;
}
@OutputDirectory
public DirectoryProperty getDestinationDirectory() {
return this.destinationDirectory;
}
@Input
public Property<String> getPluginVersion() {
return this.pluginVersion;
}
}

@ -1,60 +0,0 @@
/*
* Copyright 2023-2023 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.ArrayList;
import java.util.List;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.internal.IConventionAware;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.plugins.ide.eclipse.EclipseWtpPlugin;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
import org.gradle.plugins.ide.eclipse.model.Facet;
/**
* Conventions that are applied in the presence of the {WarPlugin}. When the plugin is
* applied:
* <ul>
* <li>Update Eclipse WTP Plugin facets to use Servlet 5.0</li>
* </ul>
*
* @author Phillip Webb
*/
public class WarConventions {
void apply(Project project) {
project.getPlugins().withType(EclipseWtpPlugin.class, (wtp) -> {
project.getTasks().getByName(EclipseWtpPlugin.ECLIPSE_WTP_FACET_TASK_NAME).doFirst((task) -> {
EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class);
((IConventionAware) eclipseModel.getWtp().getFacet()).getConventionMapping()
.map("facets", () -> getFacets(project));
});
});
}
private List<Facet> getFacets(Project project) {
JavaVersion javaVersion = project.getExtensions().getByType(JavaPluginExtension.class).getSourceCompatibility();
List<Facet> facets = new ArrayList<>();
facets.add(new Facet(Facet.FacetType.fixed, "jst.web", null));
facets.add(new Facet(Facet.FacetType.installed, "jst.web", "5.0"));
facets.add(new Facet(Facet.FacetType.installed, "jst.java", javaVersion.toString()));
return facets;
}
}

@ -1,226 +0,0 @@
/*
* Copyright 2022-2023 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.architecture;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.stream.Collectors;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClass.Predicates;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.domain.JavaParameter;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Task;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileTree;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.tasks.IgnoreEmptyDirectories;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
/**
* {@link Task} that checks for architecture problems.
*
* @author Andy Wilkinson
*/
public abstract class ArchitectureCheck extends DefaultTask {
private FileCollection classes;
public ArchitectureCheck() {
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
getRules().addAll(allPackagesShouldBeFreeOfTangles(),
allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(),
allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters(),
noClassesShouldCallStepVerifierStepVerifyComplete(),
noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList());
getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList()));
}
@TaskAction
void checkArchitecture() throws IOException {
JavaClasses javaClasses = new ClassFileImporter()
.importPaths(this.classes.getFiles().stream().map(File::toPath).toList());
List<EvaluationResult> violations = getRules().get()
.stream()
.map((rule) -> rule.evaluate(javaClasses))
.filter(EvaluationResult::hasViolation)
.toList();
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
outputFile.getParentFile().mkdirs();
if (!violations.isEmpty()) {
StringBuilder report = new StringBuilder();
for (EvaluationResult violation : violations) {
report.append(violation.getFailureReport().toString());
report.append(String.format("%n"));
}
Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
throw new GradleException("Architecture check failed. See '" + outputFile + "' for details.");
}
else {
outputFile.createNewFile();
}
}
private ArchRule allPackagesShouldBeFreeOfTangles() {
return SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles();
}
private ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization() {
return ArchRuleDefinition.methods()
.that()
.areAnnotatedWith("org.springframework.context.annotation.Bean")
.and()
.haveRawReturnType(Predicates.assignableTo("org.springframework.beans.factory.config.BeanPostProcessor"))
.should(onlyHaveParametersThatWillNotCauseEagerInitialization())
.andShould()
.beStatic()
.allowEmptyShould(true);
}
private ArchCondition<JavaMethod> onlyHaveParametersThatWillNotCauseEagerInitialization() {
DescribedPredicate<CanBeAnnotated> notAnnotatedWithLazy = DescribedPredicate
.not(CanBeAnnotated.Predicates.annotatedWith("org.springframework.context.annotation.Lazy"));
DescribedPredicate<JavaClass> notOfASafeType = DescribedPredicate
.not(Predicates.assignableTo("org.springframework.beans.factory.ObjectProvider")
.or(Predicates.assignableTo("org.springframework.context.ApplicationContext"))
.or(Predicates.assignableTo("org.springframework.core.env.Environment")));
return new ArchCondition<>("not have parameters that will cause eager initialization") {
@Override
public void check(JavaMethod item, ConditionEvents events) {
item.getParameters()
.stream()
.filter(notAnnotatedWithLazy)
.filter((parameter) -> notOfASafeType.test(parameter.getRawType()))
.forEach((parameter) -> events.add(SimpleConditionEvent.violated(parameter,
parameter.getDescription() + " will cause eager initialization as it is "
+ notAnnotatedWithLazy.getDescription() + " and is "
+ notOfASafeType.getDescription())));
}
};
}
private ArchRule allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters() {
return ArchRuleDefinition.methods()
.that()
.areAnnotatedWith("org.springframework.context.annotation.Bean")
.and()
.haveRawReturnType(
Predicates.assignableTo("org.springframework.beans.factory.config.BeanFactoryPostProcessor"))
.should(haveNoParameters())
.andShould()
.beStatic()
.allowEmptyShould(true);
}
private ArchCondition<JavaMethod> haveNoParameters() {
return new ArchCondition<>("have no parameters") {
@Override
public void check(JavaMethod item, ConditionEvents events) {
List<JavaParameter> parameters = item.getParameters();
if (!parameters.isEmpty()) {
events
.add(SimpleConditionEvent.violated(item, item.getDescription() + " should have no parameters"));
}
}
};
}
private ArchRule noClassesShouldCallStepVerifierStepVerifyComplete() {
return ArchRuleDefinition.noClasses()
.should()
.callMethod("reactor.test.StepVerifier$Step", "verifyComplete")
.because("it can block indefinitely and expectComplete().verify(Duration) should be used instead");
}
private ArchRule noClassesShouldConfigureDefaultStepVerifierTimeout() {
return ArchRuleDefinition.noClasses()
.should()
.callMethod("reactor.test.StepVerifier", "setDefaultTimeout", "java.time.Duration")
.because("expectComplete().verify(Duration) should be used instead");
}
private ArchRule noClassesShouldCallCollectorsToList() {
return ArchRuleDefinition.noClasses()
.should()
.callMethod(Collectors.class, "toList")
.because("java.util.stream.Stream.toList() should be used instead");
}
public void setClasses(FileCollection classes) {
this.classes = classes;
}
@Internal
public FileCollection getClasses() {
return this.classes;
}
@InputFiles
@SkipWhenEmpty
@IgnoreEmptyDirectories
@PathSensitive(PathSensitivity.RELATIVE)
final FileTree getInputClasses() {
return this.classes.getAsFileTree();
}
@Optional
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
public abstract DirectoryProperty getResourcesDirectory();
@OutputDirectory
public abstract DirectoryProperty getOutputDirectory();
@Internal
public abstract ListProperty<ArchRule> getRules();
@Input
// The rules themselves can't be an input as they aren't serializable so we use their
// descriptions instead
abstract ListProperty<String> getRuleDescriptions();
}

@ -1,66 +0,0 @@
/*
* Copyright 2012-2023 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.architecture;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.springframework.util.StringUtils;
/**
* {@link Plugin} for verifying a project's architecture.
*
* @author Andy Wilkinson
*/
public class ArchitecturePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaBasePlugin.class, (javaPlugin) -> registerTasks(project));
}
private void registerTasks(Project project) {
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
List<TaskProvider<ArchitectureCheck>> packageTangleChecks = new ArrayList<>();
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
TaskProvider<ArchitectureCheck> checkPackageTangles = project.getTasks()
.register("checkArchitecture" + StringUtils.capitalize(sourceSet.getName()), ArchitectureCheck.class,
(task) -> {
task.setClasses(sourceSet.getOutput().getClassesDirs());
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
+ " source set.");
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
});
packageTangleChecks.add(checkPackageTangles);
}
if (!packageTangleChecks.isEmpty()) {
TaskProvider<Task> checkTask = project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME);
checkTask.configure((check) -> check.dependsOn(packageTangleChecks));
}
}
}

@ -1,74 +0,0 @@
/*
* Copyright 2012-2023 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.artifacts;
import org.gradle.api.Project;
/**
* Information about artifacts produced by a build.
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
public final class ArtifactRelease {
private static final String SNAPSHOT = "snapshot";
private static final String MILESTONE = "milestone";
private static final String RELEASE = "release";
private static final String SPRING_REPO = "https://repo.spring.io/%s";
private static final String MAVEN_REPO = "https://repo.maven.apache.org/maven2";
private final String type;
private ArtifactRelease(String type) {
this.type = type;
}
public String getType() {
return this.type;
}
public String getDownloadRepo() {
return (this.isRelease()) ? MAVEN_REPO : String.format(SPRING_REPO, this.getType());
}
public boolean isRelease() {
return RELEASE.equals(this.type);
}
public static ArtifactRelease forProject(Project project) {
return new ArtifactRelease(determineReleaseType(project));
}
private static String determineReleaseType(Project project) {
String version = project.getVersion().toString();
int modifierIndex = version.lastIndexOf('-');
if (modifierIndex == -1) {
return RELEASE;
}
String type = version.substring(modifierIndex + 1);
if (type.startsWith("M") || type.startsWith("RC")) {
return MILESTONE;
}
return SNAPSHOT;
}
}

@ -1,149 +0,0 @@
/*
* Copyright 2012-2023 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.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
import org.gradle.api.DefaultTask;
import org.gradle.api.Task;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.springframework.asm.ClassReader;
import org.springframework.asm.Opcodes;
import org.springframework.core.CollectionFactory;
/**
* A {@link Task} for generating metadata describing a project's auto-configuration
* classes.
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
public class AutoConfigurationMetadata extends DefaultTask {
private static final String COMMENT_START = "#";
private SourceSet sourceSet;
private File outputFile;
public AutoConfigurationMetadata() {
getInputs()
.file((Callable<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"))
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("org.springframework.boot.autoconfigure.AutoConfiguration");
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 = CollectionFactory.createSortedProperties(true);
List<String> classNames = readAutoConfigurationsFile();
Set<String> publicClassNames = new LinkedHashSet<>();
for (String className : classNames) {
File classFile = findClassFile(className);
if (classFile == null) {
throw new IllegalStateException("Auto-configuration class '" + className + "' not found.");
}
try (InputStream in = new FileInputStream(classFile)) {
int access = new ClassReader(in).getAccess();
if ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC) {
publicClassNames.add(className);
}
}
}
autoConfiguration.setProperty("autoConfigurationClassNames", String.join(",", publicClassNames));
autoConfiguration.setProperty("module", getProject().getName());
return autoConfiguration;
}
/**
* Reads auto-configurations from
* META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
* @return auto-configurations
*/
private List<String> readAutoConfigurationsFile() throws IOException {
File file = new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports");
if (!file.exists()) {
return Collections.emptyList();
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
return reader.lines().map(this::stripComment).filter((line) -> !line.isEmpty()).toList();
}
}
private String stripComment(String line) {
int commentStart = line.indexOf(COMMENT_START);
if (commentStart == -1) {
return line.trim();
}
return line.substring(0, commentStart).trim();
}
private File findClassFile(String className) {
String classFileName = className.replace(".", "/") + ".class";
for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) {
File classFile = new File(classesDir, classFileName);
if (classFile.isFile()) {
return classFile;
}
}
return null;
}
}

@ -1,161 +0,0 @@
/*
* Copyright 2012-2023 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.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
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.JavaPluginExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceSet;
import org.springframework.boot.build.DeployedPlugin;
import org.springframework.boot.build.architecture.ArchitectureCheck;
import org.springframework.boot.build.architecture.ArchitecturePlugin;
/**
* {@link Plugin} for projects that define auto-configuration. When applied, the plugin
* applies the {@link DeployedPlugin}. Additionally, when the {@link JavaPlugin} is
* applied it:
*
* <ul>
* <li>Adds a dependency on the auto-configuration annotation processor.
* <li>Defines a task that produces metadata describing the auto-configuration. The
* metadata is made available as an artifact in the {@code autoConfigurationMetadata}
* configuration.
* <li>Reacts to the {@link ArchitecturePlugin} being applied and:
* <ul>
* <li>Adds a rule to the {@code checkArchitectureMain} task to verify that all
* {@code AutoConfiguration} classes are listed in the {@code AutoConfiguration.imports}
* file.
* </ul>
* </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";
private static final String AUTO_CONFIGURATION_IMPORTS_PATH = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports";
@Override
public void apply(Project project) {
project.getPlugins().apply(DeployedPlugin.class);
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
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) -> {
SourceSet main = project.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
task.setSourceSet(main);
task.dependsOn(main.getClassesTaskName());
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));
});
project.getPlugins().withType(ArchitecturePlugin.class, (architecturePlugin) -> {
project.getTasks().named("checkArchitectureMain", ArchitectureCheck.class).configure((task) -> {
SourceSet main = project.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
File resourcesDirectory = main.getOutput().getResourcesDir();
task.dependsOn(main.getProcessResourcesTaskName());
task.getInputs().files(resourcesDirectory).optional().withPathSensitivity(PathSensitivity.RELATIVE);
task.getRules()
.add(allClassesAnnotatedWithAutoConfigurationShouldBeListedInAutoConfigurationImports(
autoConfigurationImports(project, resourcesDirectory)));
});
});
});
}
private ArchRule allClassesAnnotatedWithAutoConfigurationShouldBeListedInAutoConfigurationImports(
Provider<AutoConfigurationImports> imports) {
return ArchRuleDefinition.classes()
.that()
.areAnnotatedWith("org.springframework.boot.autoconfigure.AutoConfiguration")
.should(beListedInAutoConfigurationImports(imports))
.allowEmptyShould(true);
}
private ArchCondition<JavaClass> beListedInAutoConfigurationImports(Provider<AutoConfigurationImports> imports) {
return new ArchCondition<>("be listed in " + AUTO_CONFIGURATION_IMPORTS_PATH) {
@Override
public void check(JavaClass item, ConditionEvents events) {
AutoConfigurationImports autoConfigurationImports = imports.get();
if (!autoConfigurationImports.imports.contains(item.getName())) {
events.add(SimpleConditionEvent.violated(item,
item.getName() + " was not listed in " + autoConfigurationImports.importsFile));
}
}
};
}
private Provider<AutoConfigurationImports> autoConfigurationImports(Project project, File resourcesDirectory) {
Path importsFile = new File(resourcesDirectory, AUTO_CONFIGURATION_IMPORTS_PATH).toPath();
return project.provider(() -> {
try {
return new AutoConfigurationImports(project.getProjectDir().toPath().relativize(importsFile),
Files.readAllLines(importsFile));
}
catch (IOException ex) {
throw new RuntimeException("Failed to read AutoConfiguration.imports", ex);
}
});
}
private static record AutoConfigurationImports(Path importsFile, List<String> imports) {
}
}

@ -1,136 +0,0 @@
/*
* 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.DefaultTask;
import org.gradle.api.Task;
import org.gradle.api.file.FileCollection;
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 DefaultTask {
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);
}
}
}

@ -1,495 +0,0 @@
/*
* Copyright 2012-2023 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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
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.lang.Closure;
import groovy.lang.GroovyObjectSupport;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.InvalidUserCodeException;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.JavaPlatformPlugin;
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.bom.Library.Exclusion;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.LibraryVersion;
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;
import org.springframework.boot.build.mavenplugin.MavenExec;
import org.springframework.util.FileCopyUtils;
/**
* 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<>();
private final UpgradeHandler upgradeHandler;
private final DependencyHandler dependencyHandler;
private final Project project;
public BomExtension(DependencyHandler dependencyHandler, Project project) {
this.dependencyHandler = dependencyHandler;
this.upgradeHandler = project.getObjects().newInstance(UpgradeHandler.class);
this.project = project;
}
public List<Library> getLibraries() {
return this.libraries;
}
public void upgrade(Action<UpgradeHandler> action) {
action.execute(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, Action<LibraryHandler> action) {
library(name, null, action);
}
public void library(String name, String version, Action<LibraryHandler> action) {
ObjectFactory objects = this.project.getObjects();
LibraryHandler libraryHandler = objects.newInstance(LibraryHandler.class, (version != null) ? version : "");
action.execute(libraryHandler);
LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version));
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots));
}
public void effectiveBomArtifact() {
Configuration effectiveBomConfiguration = this.project.getConfigurations().create("effectiveBom");
this.project.getTasks()
.matching((task) -> task.getName().equals(DeployedPlugin.GENERATE_POM_TASK_NAME))
.all((task) -> {
Sync syncBom = this.project.getTasks().create("syncBom", Sync.class);
syncBom.dependsOn(task);
File generatedBomDir = new File(this.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(this.project.getBuildDir(), "local-m2-repository").getAbsolutePath());
syncBom.from(this.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 = this.project.getTasks()
.create("generateEffectiveBom", MavenExec.class);
generateEffectiveBom.setProjectDir(generatedBomDir);
File effectiveBom = new File(this.project.getBuildDir(),
"generated/effective-bom/" + this.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));
this.project.getArtifacts()
.add(effectiveBomConfiguration.getName(), effectiveBom,
(artifact) -> artifact.builtBy(generateEffectiveBom));
});
}
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 classifier) {
String coordinates = groupId + ":" + artifactId + ":" + classifier;
return this.artifactVersionProperties.get(coordinates);
}
private void putArtifactVersionProperty(String groupId, String artifactId, String versionProperty) {
putArtifactVersionProperty(groupId, artifactId, null, versionProperty);
}
private void putArtifactVersionProperty(String groupId, String artifactId, String classifier,
String versionProperty) {
String coordinates = groupId + ":" + artifactId + ":" + ((classifier != null) ? classifier : "");
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);
String versionProperty = library.getVersionProperty();
if (versionProperty != null) {
this.properties.put(versionProperty, library.getVersion().getVersion());
}
for (Group group : library.getGroups()) {
for (Module module : group.getModules()) {
putArtifactVersionProperty(group.getId(), module.getName(), module.getClassifier(), versionProperty);
this.dependencyHandler.getConstraints()
.add(JavaPlatformPlugin.API_CONFIGURATION_NAME, createDependencyNotation(group.getId(),
module.getName(), library.getVersion().getVersion()));
}
for (String bomImport : group.getBoms()) {
putArtifactVersionProperty(group.getId(), bomImport, versionProperty);
String bomDependency = createDependencyNotation(group.getId(), bomImport,
library.getVersion().getVersion());
this.dependencyHandler.add(JavaPlatformPlugin.API_CONFIGURATION_NAME,
this.dependencyHandler.platform(bomDependency));
this.dependencyHandler.add(BomPlugin.API_ENFORCED_CONFIGURATION_NAME,
this.dependencyHandler.enforcedPlatform(bomDependency));
}
}
}
public static class LibraryHandler {
private final List<Group> groups = new ArrayList<>();
private final List<ProhibitedVersion> prohibitedVersions = new ArrayList<>();
private boolean considerSnapshots = false;
private String version;
private String calendarName;
@Inject
public LibraryHandler(String version) {
this.version = version;
}
public void version(String version) {
this.version = version;
}
public void considerSnapshots() {
this.considerSnapshots = true;
}
public void setCalendarName(String calendarName) {
this.calendarName = calendarName;
}
public void group(String id, Action<GroupHandler> action) {
GroupHandler groupHandler = new GroupHandler(id);
action.execute(groupHandler);
this.groups
.add(new Group(groupHandler.id, groupHandler.modules, groupHandler.plugins, groupHandler.imports));
}
public void prohibit(Action<ProhibitedHandler> action) {
ProhibitedHandler handler = new ProhibitedHandler();
action.execute(handler);
this.prohibitedVersions.add(new ProhibitedVersion(handler.versionRange, handler.startsWith,
handler.endsWith, handler.contains, handler.reason));
}
public static class ProhibitedHandler {
private String reason;
private final List<String> startsWith = new ArrayList<>();
private final List<String> endsWith = new ArrayList<>();
private final List<String> contains = new ArrayList<>();
private VersionRange versionRange;
public void versionRange(String versionRange) {
try {
this.versionRange = VersionRange.createFromVersionSpec(versionRange);
}
catch (InvalidVersionSpecificationException ex) {
throw new InvalidUserCodeException("Invalid version range", ex);
}
}
public void startsWith(String startsWith) {
this.startsWith.add(startsWith);
}
public void startsWith(Collection<String> startsWith) {
this.startsWith.addAll(startsWith);
}
public void endsWith(String endsWith) {
this.endsWith.add(endsWith);
}
public void endsWith(Collection<String> endsWith) {
this.endsWith.addAll(endsWith);
}
public void contains(String contains) {
this.contains.add(contains);
}
public void contains(List<String> contains) {
this.contains.addAll(contains);
}
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) ? module : new Module((String) input))
.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<?> closure) {
ModuleHandler moduleHandler = new ModuleHandler();
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
closure.setDelegate(moduleHandler);
closure.call(moduleHandler);
return new Module(name, moduleHandler.type, moduleHandler.classifier, moduleHandler.exclusions);
}
}
throw new InvalidUserDataException("Invalid configuration for module '" + name + "'");
}
public class ModuleHandler {
private final List<Exclusion> exclusions = new ArrayList<>();
private String type;
private String classifier;
public void exclude(Map<String, String> exclusion) {
this.exclusions.add(new Exclusion(exclusion.get("group"), exclusion.get("module")));
}
public void setType(String type) {
this.type = type;
}
public void setClassifier(String classifier) {
this.classifier = classifier;
}
}
}
}
public static class UpgradeHandler {
private UpgradePolicy upgradePolicy;
private final GitHubHandler gitHub = new GitHubHandler();
public void setPolicy(UpgradePolicy upgradePolicy) {
this.upgradePolicy = upgradePolicy;
}
public void gitHub(Action<GitHubHandler> action) {
action.execute(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 final 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;
}
}
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);
}
}
}
}

@ -1,302 +0,0 @@
/*
* Copyright 2012-2023 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.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import groovy.namespace.QName;
import groovy.util.Node;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
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.springframework.boot.build.DeployedPlugin;
import org.springframework.boot.build.MavenRepositoryPlugin;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.Module;
import org.springframework.boot.build.bom.bomr.MoveToSnapshots;
import org.springframework.boot.build.bom.bomr.UpgradeBom;
/**
* {@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> {
static final String API_ENFORCED_CONFIGURATION_NAME = "apiEnforced";
@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();
createApiEnforcedConfiguration(project);
BomExtension bom = project.getExtensions()
.create("bom", BomExtension.class, project.getDependencies(), project);
project.getTasks().create("bomrCheck", CheckBom.class, bom);
project.getTasks().create("bomrUpgrade", UpgradeBom.class, bom);
project.getTasks().create("moveToSnapshots", MoveToSnapshots.class, bom);
new PublishingCustomizer(project, bom).customize();
}
private void createApiEnforcedConfiguration(Project project) {
Configuration apiEnforced = project.getConfigurations()
.create(API_ENFORCED_CONFIGURATION_NAME, (configuration) -> {
configuration.setCanBeConsumed(false);
configuration.setCanBeResolved(false);
configuration.setVisible(false);
});
project.getConfigurations()
.getByName(JavaPlatformPlugin.ENFORCED_API_ELEMENTS_CONFIGURATION_NAME)
.extendsFrom(apiEnforced);
project.getConfigurations()
.getByName(JavaPlatformPlugin.ENFORCED_RUNTIME_ELEMENTS_CONFIGURATION_NAME)
.extendsFrom(apiEnforced);
}
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);
addClassifiedManagedDependencies(dependencyManagement);
replaceVersionsWithVersionPropertyReferences(dependencyManagement);
addExclusionsToManagedDependencies(dependencyManagement);
addTypesToManagedDependencies(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();
Node classifierNode = findChild(dependency, "classifier");
String classifier = (classifierNode != null) ? classifierNode.text() : "";
String versionProperty = this.bom.getArtifactVersionProperty(groupId, artifactId, classifier);
if (versionProperty != null) {
findChild(dependency, "version").setValue("${" + versionProperty + "}");
}
}
}
}
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 addTypesToManagedDependencies(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();
Set<String> types = 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))
.map(Module::getType)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (types.size() > 1) {
throw new IllegalStateException(
"Multiple types for " + groupId + ":" + artifactId + ": " + types);
}
if (types.size() == 1) {
String type = types.iterator().next();
dependency.appendNode("type", type);
}
}
}
}
@SuppressWarnings("unchecked")
private void addClassifiedManagedDependencies(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();
String version = findChild(dependency, "version").text();
Set<String> classifiers = 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))
.map(Module::getClassifier)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Node target = dependency;
for (String classifier : classifiers) {
if (classifier.length() > 0) {
if (target == null) {
target = new Node(null, "dependency");
target.appendNode("groupId", groupId);
target.appendNode("artifactId", artifactId);
target.appendNode("version", version);
int index = dependency.parent().children().indexOf(dependency);
dependency.parent().children().add(index + 1, target);
}
target.appendNode("classifier", classifier);
}
target = null;
}
}
}
}
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);
String versionProperty = library.getVersionProperty();
String value = (versionProperty != null) ? "${" + versionProperty + "}"
: library.getVersion().getVersion().toString();
plugin.appendNode("version", value);
}
}
}
}
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) {
if ((node.name() instanceof QName qname) && name.equals(qname.getLocalPart())) {
return node;
}
if (name.equals(node.name())) {
return node;
}
}
}
return null;
}
@SuppressWarnings("unchecked")
private List<Node> findChildren(Node parent, String name) {
return parent.children().stream().filter((child) -> isNodeWithName(child, name)).toList();
}
private boolean isNodeWithName(Object candidate, String name) {
if (candidate instanceof Node node) {
if ((node.name() instanceof QName qname) && name.equals(qname.getLocalPart())) {
return true;
}
if (name.equals(node.name())) {
return true;
}
}
return false;
}
}
}

@ -1,95 +0,0 @@
/*
* Copyright 2012-2023 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().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)) {
if (exclusion.endsWith(":*")) {
String group = exclusion.substring(0, exclusion.indexOf(':') + 1);
if (resolved.stream().noneMatch((candidate) -> candidate.startsWith(group))) {
unused.add(exclusion);
}
}
else {
unused.add(exclusion);
}
}
}
exclusions.removeAll(resolved);
if (!unused.isEmpty()) {
throw new InvalidUserDataException(
"Unnecessary exclusions on " + groupId + ":" + module.getName() + ": " + exclusions);
}
}
}

@ -1,272 +0,0 @@
/*
* Copyright 2012-2023 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 String calendarName;
private final LibraryVersion version;
private final List<Group> groups;
private final String versionProperty;
private final List<ProhibitedVersion> prohibitedVersions;
private final boolean considerSnapshots;
/**
* Create a new {@code Library} with the given {@code name}, {@code version}, and
* {@code groups}.
* @param name name of the library
* @param calendarName name of the library as it appears in the Spring Calendar. May
* be {@code null} in which case the {@code name} is used.
* @param version version of the library
* @param groups groups in the library
* @param prohibitedVersions version of the library that are prohibited
* @param considerSnapshots whether to consider snapshots
*/
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots) {
this.name = name;
this.calendarName = (calendarName != null) ? calendarName : name;
this.version = version;
this.groups = groups;
this.versionProperty = "Spring Boot".equals(name) ? null
: name.toLowerCase(Locale.ENGLISH).replace(' ', '-') + ".version";
this.prohibitedVersions = prohibitedVersions;
this.considerSnapshots = considerSnapshots;
}
public String getName() {
return this.name;
}
public String getCalendarName() {
return this.calendarName;
}
public LibraryVersion getVersion() {
return this.version;
}
public List<Group> getGroups() {
return this.groups;
}
public String getVersionProperty() {
return this.versionProperty;
}
public List<ProhibitedVersion> getProhibitedVersions() {
return this.prohibitedVersions;
}
public boolean isConsiderSnapshots() {
return this.considerSnapshots;
}
/**
* 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 List<String> startsWith;
private final List<String> endsWith;
private final List<String> contains;
private final String reason;
public ProhibitedVersion(VersionRange range, List<String> startsWith, List<String> endsWith,
List<String> contains, String reason) {
this.range = range;
this.startsWith = startsWith;
this.endsWith = endsWith;
this.contains = contains;
this.reason = reason;
}
public VersionRange getRange() {
return this.range;
}
public List<String> getStartsWith() {
return this.startsWith;
}
public List<String> getEndsWith() {
return this.endsWith;
}
public List<String> getContains() {
return this.contains;
}
public String getReason() {
return this.reason;
}
}
public static class LibraryVersion {
private final DependencyVersion version;
public LibraryVersion(DependencyVersion version) {
this.version = version;
}
public DependencyVersion getVersion() {
return this.version;
}
}
/**
* 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 String type;
private final String classifier;
private final List<Exclusion> exclusions;
public Module(String name) {
this(name, Collections.emptyList());
}
public Module(String name, String type) {
this(name, type, null, Collections.emptyList());
}
public Module(String name, List<Exclusion> exclusions) {
this(name, null, null, exclusions);
}
public Module(String name, String type, String classifier, List<Exclusion> exclusions) {
this.name = name;
this.type = type;
this.classifier = (classifier != null) ? classifier : "";
this.exclusions = exclusions;
}
public String getName() {
return this.name;
}
public String getClassifier() {
return this.classifier;
}
public String getType() {
return this.type;
}
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;
}
}
}

@ -1,56 +0,0 @@
/*
* Copyright 2012-2023 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> {
/**
* Any version.
*/
ANY((candidate, current) -> true),
/**
* Minor versions of the current major version.
*/
SAME_MAJOR_VERSION((candidate, current) -> candidate.isSameMajor(current)),
/**
* Patch versions of the current minor version.
*/
SAME_MINOR_VERSION((candidate, current) -> candidate.isSameMinor(current));
private final 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);
}
}

@ -1,70 +0,0 @@
/*
* Copyright 2012-2023 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
import org.springframework.boot.build.bom.Library;
/**
* 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 UserInputHandler userInputHandler;
private final LibraryUpdateResolver libraryUpdateResolver;
InteractiveUpgradeResolver(UserInputHandler userInputHandler, LibraryUpdateResolver libraryUpdateResolver) {
this.userInputHandler = userInputHandler;
this.libraryUpdateResolver = libraryUpdateResolver;
}
@Override
public List<Upgrade> resolveUpgrades(Collection<Library> librariesToUpgrade, Collection<Library> libraries) {
Map<String, Library> librariesByName = new HashMap<>();
for (Library library : libraries) {
librariesByName.put(library.getName(), library);
}
List<LibraryWithVersionOptions> libraryUpdates = this.libraryUpdateResolver
.findLibraryUpdates(librariesToUpgrade, librariesByName);
return libraryUpdates.stream().map(this::resolveUpgrade).filter(Objects::nonNull).toList();
}
private Upgrade resolveUpgrade(LibraryWithVersionOptions libraryWithVersionOptions) {
if (libraryWithVersionOptions.getVersionOptions().isEmpty()) {
return null;
}
VersionOption current = new VersionOption(libraryWithVersionOptions.getLibrary().getVersion().getVersion());
VersionOption selected = this.userInputHandler.selectOption(
libraryWithVersionOptions.getLibrary().getName() + " "
+ libraryWithVersionOptions.getLibrary().getVersion().getVersion(),
libraryWithVersionOptions.getVersionOptions(), current);
return (selected.equals(current)) ? null
: new Upgrade(libraryWithVersionOptions.getLibrary(), selected.getVersion());
}
}

@ -1,41 +0,0 @@
/*
* Copyright 2012-2023 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 java.util.Map;
import org.springframework.boot.build.bom.Library;
/**
* Resolves library updates.
*
* @author Moritz Halbritter
*/
public interface LibraryUpdateResolver {
/**
* Finds library updates.
* @param librariesToUpgrade libraries to update
* @param librariesByName libraries indexed by name
* @return library which have updates
*/
List<LibraryWithVersionOptions> findLibraryUpdates(Collection<Library> librariesToUpgrade,
Map<String, Library> librariesByName);
}

@ -1,42 +0,0 @@
/*
* Copyright 2012-2023 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.List;
import org.springframework.boot.build.bom.Library;
class LibraryWithVersionOptions {
private final Library library;
private final List<VersionOption> versionOptions;
LibraryWithVersionOptions(Library library, List<VersionOption> versionOptions) {
this.library = library;
this.versionOptions = versionOptions;
}
Library getLibrary() {
return this.library;
}
List<VersionOption> getVersionOptions() {
return this.versionOptions;
}
}

@ -1,112 +0,0 @@
/*
* Copyright 2012-2023 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.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
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 Collection<URI> repositoryUrls;
MavenMetadataVersionResolver(Collection<URI> repositoryUrls) {
this(new RestTemplate(Collections.singletonList(new StringHttpMessageConverter())), repositoryUrls);
}
MavenMetadataVersionResolver(RestTemplate restTemplate, Collection<URI> repositoryUrls) {
this.rest = restTemplate;
this.repositoryUrls = normalize(repositoryUrls);
}
private Collection<URI> normalize(Collection<URI> uris) {
return uris.stream().map(this::normalize).toList();
}
private URI normalize(URI uri) {
if ("/".equals(uri.getPath())) {
return uri;
}
return URI.create(uri.toString() + "/");
}
@Override
public SortedSet<DependencyVersion> resolveVersions(String groupId, String artifactId) {
Set<String> versions = new HashSet<>();
for (URI repositoryUrl : this.repositoryUrls) {
versions.addAll(resolveVersions(groupId, artifactId, repositoryUrl));
}
return versions.stream().map(DependencyVersion::parse).collect(Collectors.toCollection(TreeSet::new));
}
private Set<String> resolveVersions(String groupId, String artifactId, URI repositoryUrl) {
Set<String> versions = new HashSet<>();
URI url = repositoryUrl.resolve(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;
}
}

@ -1,108 +0,0 @@
/*
* Copyright 2021-2023 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.net.URI;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
import javax.inject.Inject;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.build.bom.BomExtension;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.bomr.ReleaseSchedule.Release;
import org.springframework.boot.build.bom.bomr.github.Milestone;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
/**
* A {@link Task} to move to snapshot dependencies.
*
* @author Andy Wilkinson
*/
public abstract class MoveToSnapshots extends UpgradeDependencies {
private static final Logger log = LoggerFactory.getLogger(MoveToSnapshots.class);
private final URI REPOSITORY_URI = URI.create("https://repo.spring.io/snapshot/");
@Inject
public MoveToSnapshots(BomExtension bom) {
super(bom, true);
getRepositoryUris().add(this.REPOSITORY_URI);
}
@Override
@TaskAction
void upgradeDependencies() {
super.upgradeDependencies();
}
@Override
protected String issueTitle(Upgrade upgrade) {
String snapshotVersion = upgrade.getVersion().toString();
String releaseVersion = snapshotVersion.substring(0, snapshotVersion.length() - "-SNAPSHOT".length());
return "Upgrade to " + upgrade.getLibrary().getName() + " " + releaseVersion;
}
@Override
protected String commitMessage(Upgrade upgrade, int issueNumber) {
return "Start building against " + upgrade.getLibrary().getName() + " " + releaseVersion(upgrade) + " snapshots"
+ "\n\nSee gh-" + issueNumber;
}
private String releaseVersion(Upgrade upgrade) {
String snapshotVersion = upgrade.getVersion().toString();
return snapshotVersion.substring(0, snapshotVersion.length() - "-SNAPSHOT".length());
}
@Override
protected boolean eligible(Library library) {
return library.isConsiderSnapshots() && super.eligible(library);
}
@Override
protected List<BiPredicate<Library, DependencyVersion>> determineUpdatePredicates(Milestone milestone) {
ReleaseSchedule releaseSchedule = new ReleaseSchedule();
Map<String, List<Release>> releases = releaseSchedule.releasesBetween(OffsetDateTime.now(),
milestone.getDueOn());
List<BiPredicate<Library, DependencyVersion>> predicates = super.determineUpdatePredicates(milestone);
predicates.add((library, candidate) -> {
List<Release> releasesForLibrary = releases.get(library.getCalendarName());
if (releasesForLibrary != null) {
for (Release release : releasesForLibrary) {
if (candidate.isSnapshotFor(release.getVersion())) {
return true;
}
}
}
if (log.isInfoEnabled()) {
log.info("Ignoring " + candidate + ". No release of " + library.getName() + " scheduled before "
+ milestone.getDueOn());
}
return false;
});
return predicates;
}
}

@ -1,84 +0,0 @@
/*
* Copyright 2012-2023 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.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.build.bom.Library;
/**
* {@link LibraryUpdateResolver} decorator that uses multiple threads to find library
* updates.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
class MultithreadedLibraryUpdateResolver implements LibraryUpdateResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(MultithreadedLibraryUpdateResolver.class);
private final int threads;
private final LibraryUpdateResolver delegate;
MultithreadedLibraryUpdateResolver(int threads, LibraryUpdateResolver delegate) {
this.threads = threads;
this.delegate = delegate;
}
@Override
public List<LibraryWithVersionOptions> findLibraryUpdates(Collection<Library> librariesToUpgrade,
Map<String, Library> librariesByName) {
LOGGER.info("Looking for updates using {} threads", this.threads);
ExecutorService executorService = Executors.newFixedThreadPool(this.threads);
try {
return librariesToUpgrade.stream()
.map((library) -> executorService.submit(
() -> this.delegate.findLibraryUpdates(Collections.singletonList(library), librariesByName)))
.flatMap(this::getResult)
.toList();
}
finally {
executorService.shutdownNow();
}
}
private Stream<LibraryWithVersionOptions> getResult(Future<List<LibraryWithVersionOptions>> job) {
try {
return job.get().stream();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new RuntimeException(ex);
}
catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
}

@ -1,108 +0,0 @@
/*
* Copyright 2023-2023 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.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
/**
* Release schedule for Spring projects, retrieved from
* <a href="https://calendar.spring.io">https://calendar.spring.io</a>.
*
* @author Andy Wilkinson
*/
class ReleaseSchedule {
private static final Pattern LIBRARY_AND_VERSION = Pattern.compile("([A-Za-z0-9 ]+) ([0-9A-Za-z.-]+)");
private final RestOperations rest;
ReleaseSchedule() {
this(new RestTemplate());
}
ReleaseSchedule(RestOperations rest) {
this.rest = rest;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Map<String, List<Release>> releasesBetween(OffsetDateTime start, OffsetDateTime end) {
ResponseEntity<List> response = this.rest
.getForEntity("https://calendar.spring.io/releases?start=" + start + "&end=" + end, List.class);
List<Map<String, String>> body = response.getBody();
Map<String, List<Release>> releasesByLibrary = new LinkedCaseInsensitiveMap<>();
body.stream()
.map(this::asRelease)
.filter(Objects::nonNull)
.forEach((release) -> releasesByLibrary.computeIfAbsent(release.getLibraryName(), (l) -> new ArrayList<>())
.add(release));
return releasesByLibrary;
}
private Release asRelease(Map<String, String> entry) {
LocalDate due = LocalDate.parse(entry.get("start"));
String title = entry.get("title");
Matcher matcher = LIBRARY_AND_VERSION.matcher(title);
if (!matcher.matches()) {
return null;
}
String library = matcher.group(1);
String version = matcher.group(2);
return new Release(library, DependencyVersion.parse(version), due);
}
static class Release {
private final String libraryName;
private final DependencyVersion version;
private final LocalDate dueOn;
Release(String libraryName, DependencyVersion version, LocalDate dueOn) {
this.libraryName = libraryName;
this.version = version;
this.dueOn = dueOn;
}
String getLibraryName() {
return this.libraryName;
}
DependencyVersion getVersion() {
return this.version;
}
LocalDate getDueOn() {
return this.dueOn;
}
}
}

@ -1,122 +0,0 @@
/*
* Copyright 2012-2023 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.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.function.BiPredicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.bomr.version.DependencyVersion;
/**
* Standard implementation for {@link LibraryUpdateResolver}.
*
* @author Andy Wilkinson
*/
class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(StandardLibraryUpdateResolver.class);
private final VersionResolver versionResolver;
private final BiPredicate<Library, DependencyVersion> predicate;
StandardLibraryUpdateResolver(VersionResolver versionResolver,
List<BiPredicate<Library, DependencyVersion>> predicates) {
this.versionResolver = versionResolver;
this.predicate = (library, dependencyVersion) -> predicates.stream()
.allMatch((predicate) -> predicate.test(library, dependencyVersion));
}
@Override
public List<LibraryWithVersionOptions> findLibraryUpdates(Collection<Library> librariesToUpgrade,
Map<String, Library> librariesByName) {
List<LibraryWithVersionOptions> result = new ArrayList<>();
for (Library library : librariesToUpgrade) {
if (isLibraryExcluded(library)) {
continue;
}
LOGGER.info("Looking for updates for {}", library.getName());
long start = System.nanoTime();
List<VersionOption> versionOptions = getVersionOptions(library, librariesByName);
result.add(new LibraryWithVersionOptions(library, versionOptions));
LOGGER.info("Found {} updates for {}, took {}", versionOptions.size(), library.getName(),
Duration.ofNanos(System.nanoTime() - start));
}
return result;
}
protected boolean isLibraryExcluded(Library library) {
return library.getName().equals("Spring Boot");
}
protected List<VersionOption> getVersionOptions(Library library, Map<String, Library> libraries) {
return determineResolvedVersionOptions(library);
}
private List<VersionOption> determineResolvedVersionOptions(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));
}
for (String bom : group.getBoms()) {
moduleVersions.put(group.getId() + ":" + bom, getLaterVersionsForModule(group.getId(), bom, library));
}
for (String plugin : group.getPlugins()) {
moduleVersions.put(group.getId() + ":" + plugin,
getLaterVersionsForModule(group.getId(), plugin, library));
}
}
return moduleVersions.values()
.stream()
.flatMap(SortedSet::stream)
.distinct()
.filter((dependencyVersion) -> this.predicate.test(library, dependencyVersion))
.map((version) -> (VersionOption) new VersionOption.ResolvedVersionOption(version,
getMissingModules(moduleVersions, version)))
.toList();
}
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, Library library) {
return this.versionResolver.resolveVersions(groupId, artifactId);
}
}

@ -1,46 +0,0 @@
/*
* Copyright 2012-2022 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;
}
}

@ -1,87 +0,0 @@
/*
* Copyright 2012-2023 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.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* {@code UpgradeApplicator} is used to apply an {@link Upgrade}. Modifies the bom
* configuration in the build file or a version property in {@code gradle.properties}.
*
* @author Andy Wilkinson
*/
class UpgradeApplicator {
private final Path buildFile;
private final Path gradleProperties;
UpgradeApplicator(Path buildFile, Path gradleProperties) {
this.buildFile = buildFile;
this.gradleProperties = gradleProperties;
}
Path apply(Upgrade upgrade) throws IOException {
String buildFileContents = Files.readString(this.buildFile);
Matcher matcher = Pattern.compile("library\\(\"" + upgrade.getLibrary().getName() + "\", \"(.+)\"\\)")
.matcher(buildFileContents);
if (!matcher.find()) {
matcher = Pattern
.compile("library\\(\"" + upgrade.getLibrary().getName() + "\"\\) \\{\\s+version\\(\"(.+)\"\\)",
Pattern.MULTILINE)
.matcher(buildFileContents);
if (!matcher.find()) {
throw new IllegalStateException("Failed to find definition for library '"
+ upgrade.getLibrary().getName() + "' in bom '" + this.buildFile + "'");
}
}
String version = matcher.group(1);
if (version.startsWith("${") && version.endsWith("}")) {
updateGradleProperties(upgrade, version);
return this.gradleProperties;
}
else {
updateBuildFile(upgrade, buildFileContents, matcher.start(1), matcher.end(1));
return this.buildFile;
}
}
private void updateGradleProperties(Upgrade upgrade, String version) throws IOException {
String property = version.substring(2, version.length() - 1);
String gradlePropertiesContents = Files.readString(this.gradleProperties);
String modified = gradlePropertiesContents.replace(
property + "=" + upgrade.getLibrary().getVersion().getVersion(), property + "=" + upgrade.getVersion());
overwrite(this.gradleProperties, modified);
}
private void updateBuildFile(Upgrade upgrade, String buildFileContents, int versionStart, int versionEnd)
throws IOException {
String modified = buildFileContents.substring(0, versionStart) + upgrade.getVersion()
+ buildFileContents.substring(versionEnd);
overwrite(this.buildFile, modified);
}
private void overwrite(Path target, String content) throws IOException {
Files.writeString(target, content, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
}

@ -1,57 +0,0 @@
/*
* Copyright 2012-2023 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.net.URI;
import javax.inject.Inject;
import org.gradle.api.Task;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import org.springframework.boot.build.bom.BomExtension;
/**
* {@link Task} to upgrade the libraries managed by a bom.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
*/
public abstract class UpgradeBom extends UpgradeDependencies {
@Inject
public UpgradeBom(BomExtension bom) {
super(bom);
getProject().getRepositories().withType(MavenArtifactRepository.class, (repository) -> {
URI repositoryUrl = repository.getUrl();
if (!repositoryUrl.toString().endsWith("snapshot")) {
getRepositoryUris().add(repositoryUrl);
}
});
}
@Override
protected String issueTitle(Upgrade upgrade) {
return "Upgrade to " + upgrade.getLibrary().getName() + " " + upgrade.getVersion();
}
@Override
protected String commitMessage(Upgrade upgrade, int issueNumber) {
return issueTitle(upgrade) + "\n\nCloses gh-" + issueNumber;
}
}

@ -1,283 +0,0 @@
/*
* Copyright 2012-2023 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.net.URI;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import javax.inject.Inject;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.gradle.api.DefaultTask;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
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.Library;
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
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.Issue;
import org.springframework.boot.build.bom.bomr.github.Milestone;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.StringUtils;
/**
* Base class for tasks that upgrade dependencies in a bom.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
*/
public abstract class UpgradeDependencies extends DefaultTask {
private final BomExtension bom;
private final boolean movingToSnapshots;
@Inject
public UpgradeDependencies(BomExtension bom) {
this(bom, false);
}
protected UpgradeDependencies(BomExtension bom, boolean movingToSnapshots) {
this.bom = bom;
getThreads().convention(2);
this.movingToSnapshots = movingToSnapshots;
}
@Input
@Option(option = "milestone", description = "Milestone to which dependency upgrade issues should be assigned")
public abstract Property<String> getMilestone();
@Input
@Optional
@Option(option = "threads", description = "Number of Threads to use for update resolution")
public abstract Property<Integer> getThreads();
@Input
@Optional
@Option(option = "libraries", description = "Regular expression that identifies the libraries to upgrade")
public abstract Property<String> getLibraries();
@Input
abstract ListProperty<URI> getRepositoryUris();
@TaskAction
void upgradeDependencies() {
GitHubRepository repository = createGitHub().getRepository(this.bom.getUpgrade().getGitHub().getOrganization(),
this.bom.getUpgrade().getGitHub().getRepository());
List<String> issueLabels = verifyLabels(repository);
Milestone milestone = determineMilestone(repository);
List<Upgrade> upgrades = resolveUpgrades(milestone);
applyUpgrades(repository, issueLabels, milestone, upgrades);
}
private void applyUpgrades(GitHubRepository repository, List<String> issueLabels, Milestone milestone,
List<Upgrade> upgrades) {
Path buildFile = getProject().getBuildFile().toPath();
Path gradleProperties = new File(getProject().getRootProject().getProjectDir(), "gradle.properties").toPath();
UpgradeApplicator upgradeApplicator = new UpgradeApplicator(buildFile, gradleProperties);
List<Issue> existingUpgradeIssues = repository.findIssues(issueLabels, milestone);
System.out.println("Applying upgrades...");
System.out.println("");
for (Upgrade upgrade : upgrades) {
System.out.println(upgrade.getLibrary().getName() + " " + upgrade.getVersion());
String title = issueTitle(upgrade);
Issue existingUpgradeIssue = findExistingUpgradeIssue(existingUpgradeIssues, upgrade);
try {
Path modified = upgradeApplicator.apply(upgrade);
int issueNumber = getOrOpenUpgradeIssue(repository, issueLabels, milestone, title,
existingUpgradeIssue);
if (existingUpgradeIssue != null && existingUpgradeIssue.getState() == Issue.State.CLOSED) {
existingUpgradeIssue.label(Arrays.asList("type: task", "status: superseded"));
}
System.out.println(" Issue: " + issueNumber + " - " + title
+ getExistingUpgradeIssueMessageDetails(existingUpgradeIssue));
if (new ProcessBuilder().command("git", "add", modified.toFile().getAbsolutePath())
.start()
.waitFor() != 0) {
throw new IllegalStateException("git add failed");
}
String commitMessage = commitMessage(upgrade, issueNumber);
if (new ProcessBuilder().command("git", "commit", "-m", commitMessage).start().waitFor() != 0) {
throw new IllegalStateException("git commit failed");
}
System.out.println(" Commit: " + commitMessage.substring(0, commitMessage.indexOf('\n')));
}
catch (IOException ex) {
throw new TaskExecutionException(this, ex);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
private int getOrOpenUpgradeIssue(GitHubRepository repository, List<String> issueLabels, Milestone milestone,
String title, Issue existingUpgradeIssue) {
if (existingUpgradeIssue != null && existingUpgradeIssue.getState() == Issue.State.OPEN) {
return existingUpgradeIssue.getNumber();
}
String body = (existingUpgradeIssue != null) ? "Supersedes #" + existingUpgradeIssue.getNumber() : "";
return repository.openIssue(title, body, issueLabels, milestone);
}
private String getExistingUpgradeIssueMessageDetails(Issue existingUpgradeIssue) {
if (existingUpgradeIssue == null) {
return "";
}
if (existingUpgradeIssue.getState() != Issue.State.CLOSED) {
return " (completes existing upgrade)";
}
return " (supersedes #" + existingUpgradeIssue.getNumber() + " " + existingUpgradeIssue.getTitle() + ")";
}
private List<String> verifyLabels(GitHubRepository repository) {
Set<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));
}
return issueLabels;
}
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 Milestone determineMilestone(GitHubRepository repository) {
List<Milestone> milestones = repository.getMilestones();
java.util.Optional<Milestone> matchingMilestone = milestones.stream()
.filter((milestone) -> milestone.getName().equals(getMilestone().get()))
.findFirst();
if (!matchingMilestone.isPresent()) {
throw new InvalidUserDataException("Unknown milestone: " + getMilestone().get());
}
return matchingMilestone.get();
}
private Issue findExistingUpgradeIssue(List<Issue> existingUpgradeIssues, Upgrade upgrade) {
String toMatch = "Upgrade to " + upgrade.getLibrary().getName();
for (Issue existingUpgradeIssue : existingUpgradeIssues) {
String title = existingUpgradeIssue.getTitle();
int lastSpaceIndex = title.lastIndexOf(' ');
if (lastSpaceIndex > -1) {
title = title.substring(0, lastSpaceIndex);
}
if (title.equals(toMatch)) {
return existingUpgradeIssue;
}
}
return null;
}
@SuppressWarnings("deprecation")
private List<Upgrade> resolveUpgrades(Milestone milestone) {
List<Upgrade> upgrades = new InteractiveUpgradeResolver(getServices().get(UserInputHandler.class),
new MultithreadedLibraryUpdateResolver(getThreads().get(),
new StandardLibraryUpdateResolver(new MavenMetadataVersionResolver(getRepositoryUris().get()),
determineUpdatePredicates(milestone))))
.resolveUpgrades(matchingLibraries(), this.bom.getLibraries());
return upgrades;
}
protected List<BiPredicate<Library, DependencyVersion>> determineUpdatePredicates(Milestone milestone) {
List<BiPredicate<Library, DependencyVersion>> updatePredicates = new ArrayList<>();
updatePredicates.add(this::compilesWithUpgradePolicy);
updatePredicates.add(this::isAnUpgrade);
updatePredicates.add(this::isNotProhibited);
return updatePredicates;
}
private boolean compilesWithUpgradePolicy(Library library, DependencyVersion candidate) {
return this.bom.getUpgrade().getPolicy().test(candidate, library.getVersion().getVersion());
}
private boolean isAnUpgrade(Library library, DependencyVersion candidate) {
return library.getVersion().getVersion().isUpgrade(candidate, this.movingToSnapshots);
}
private boolean isNotProhibited(Library library, DependencyVersion candidate) {
return !library.getProhibitedVersions()
.stream()
.anyMatch((prohibited) -> isProhibited(prohibited, candidate.toString()));
}
private boolean isProhibited(ProhibitedVersion prohibited, String candidate) {
boolean result = false;
VersionRange range = prohibited.getRange();
result = result || (range != null && range.containsVersion(new DefaultArtifactVersion(candidate)));
result = result || prohibited.getStartsWith().stream().anyMatch(candidate::startsWith);
result = result || prohibited.getStartsWith().stream().anyMatch(candidate::endsWith);
result = result || prohibited.getStartsWith().stream().anyMatch(candidate::contains);
return result;
}
private List<Library> matchingLibraries() {
List<Library> matchingLibraries = this.bom.getLibraries().stream().filter(this::eligible).toList();
if (matchingLibraries.isEmpty()) {
throw new InvalidUserDataException("No libraries to upgrade");
}
return matchingLibraries;
}
protected boolean eligible(Library library) {
String pattern = getLibraries().getOrNull();
if (pattern == null) {
return true;
}
Predicate<String> libraryPredicate = Pattern.compile(pattern).asPredicate();
return libraryPredicate.test(library.getName());
}
protected abstract String issueTitle(Upgrade upgrade);
protected abstract String commitMessage(Upgrade upgrade, int issueNumber);
}

@ -1,39 +0,0 @@
/*
* Copyright 2012-2022 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 librariesToUpgrade the libraries to upgrade
* @param libraries all libraries
* @return the upgrades
*/
List<Upgrade> resolveUpgrades(Collection<Library> librariesToUpgrade, Collection<Library> libraries);
}

@ -1,84 +0,0 @@
/*
* Copyright 2012-2023 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.List;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.StringUtils;
/**
* An option for a library update.
*
* @author Andy Wilkinson
*/
class VersionOption {
private final DependencyVersion version;
VersionOption(DependencyVersion version) {
this.version = version;
}
DependencyVersion getVersion() {
return this.version;
}
@Override
public String toString() {
return this.version.toString();
}
static final class AlignedVersionOption extends VersionOption {
private final Library alignedWith;
AlignedVersionOption(DependencyVersion version, Library alignedWith) {
super(version);
this.alignedWith = alignedWith;
}
@Override
public String toString() {
return super.toString() + " (aligned with " + this.alignedWith.getName() + " "
+ this.alignedWith.getVersion().getVersion() + ")";
}
}
static final class ResolvedVersionOption extends VersionOption {
private final List<String> missingModules;
ResolvedVersionOption(DependencyVersion version, List<String> missingModules) {
super(version);
this.missingModules = missingModules;
}
@Override
public String toString() {
if (this.missingModules.isEmpty()) {
return super.toString();
}
return super.toString() + " (some modules are missing: "
+ StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")";
}
}
}

@ -1,39 +0,0 @@
/*
* 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);
}

@ -1,46 +0,0 @@
/*
* 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);
}
}

@ -1,61 +0,0 @@
/*
* Copyright 2012-2023 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;
import java.util.Set;
/**
* 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 body the body 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, String body, List<String> labels, Milestone milestone);
/**
* Returns the labels in the repository.
* @return the labels
*/
Set<String> getLabels();
/**
* Returns the milestones in the repository.
* @return the milestones
*/
List<Milestone> getMilestones();
/**
* Finds issues that have the given {@code labels} and are assigned to the given
* {@code milestone}.
* @param labels issue labels
* @param milestone assigned milestone
* @return the matching issues
*/
List<Issue> findIssues(List<String> labels, Milestone milestone);
}

@ -1,94 +0,0 @@
/*
* Copyright 2012-2023 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.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.web.client.RestTemplate;
/**
* Minimal representation of a GitHub issue.
*
* @author Andy Wilkinson
*/
public class Issue {
private final RestTemplate rest;
private final int number;
private final String title;
private final State state;
Issue(RestTemplate rest, int number, String title, State state) {
this.rest = rest;
this.number = number;
this.title = title;
this.state = state;
}
public int getNumber() {
return this.number;
}
public String getTitle() {
return this.title;
}
public State getState() {
return this.state;
}
/**
* Labels the issue with the given {@code labels}. Any existing labels are removed.
* @param labels the labels to apply to the issue
*/
public void label(List<String> labels) {
Map<String, List<String>> body = Collections.singletonMap("labels", labels);
this.rest.put("issues/" + this.number + "/labels", body);
}
public enum State {
/**
* The issue is open.
*/
OPEN,
/**
* The issue is closed.
*/
CLOSED;
static State of(String state) {
if ("open".equals(state)) {
return OPEN;
}
if ("closed".equals(state)) {
return CLOSED;
}
else {
throw new IllegalArgumentException("Unknown state '" + state + "'");
}
}
}
}

@ -1,65 +0,0 @@
/*
* Copyright 2012-2023 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.time.OffsetDateTime;
/**
* A milestone in a {@link GitHubRepository GitHub repository}.
*
* @author Andy Wilkinson
*/
public class Milestone {
private final String name;
private final int number;
private final OffsetDateTime dueOn;
Milestone(String name, int number, OffsetDateTime dueOn) {
this.name = name;
this.number = number;
this.dueOn = dueOn;
}
/**
* 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;
}
public OffsetDateTime getDueOn() {
return this.dueOn;
}
@Override
public String toString() {
return this.name + " (" + this.number + ")";
}
}

@ -1,64 +0,0 @@
/*
* Copyright 2012-2023 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.Base64;
import java.util.Collections;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
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(
Collections.singletonList(new MappingJackson2HttpMessageConverter(new ObjectMapper())));
restTemplate.getInterceptors().add((request, body, execution) -> {
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);
}
}

@ -1,108 +0,0 @@
/*
* Copyright 2012-2023 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.time.Duration;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException.Forbidden;
import org.springframework.web.client.RestClientException;
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, String body, List<String> labels, Milestone milestone) {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("title", title);
if (milestone != null) {
requestBody.put("milestone", milestone.getNumber());
}
if (!labels.isEmpty()) {
requestBody.put("labels", labels);
}
requestBody.put("body", body);
try {
ResponseEntity<Map> response = this.rest.postForEntity("issues", requestBody, Map.class);
// See gh-30304
sleep(Duration.ofSeconds(3));
return (Integer) response.getBody().get("number");
}
catch (RestClientException ex) {
if (ex instanceof Forbidden forbidden) {
System.out.println("Received 403 response with headers " + forbidden.getResponseHeaders());
}
throw ex;
}
}
@Override
public Set<String> getLabels() {
return new HashSet<>(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"),
(milestone.get("due_on") != null) ? OffsetDateTime.parse((String) milestone.get("due_on")) : null));
}
@Override
public List<Issue> findIssues(List<String> labels, Milestone milestone) {
return get(
"issues?per_page=100&state=all&labels=" + String.join(",", labels) + "&milestone="
+ milestone.getNumber(),
(issue) -> new Issue(this.rest, (Integer) issue.get("number"), (String) issue.get("title"),
Issue.State.of((String) issue.get("state"))));
}
@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);
return ((List<Map<String, Object>>) response.getBody()).stream().map(mapper).toList();
}
private static void sleep(Duration duration) {
try {
Thread.sleep(duration.toMillis());
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}

@ -1,77 +0,0 @@
/*
* Copyright 2012-2023 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 otherVersion)
? otherVersion.comparableVersion : new ComparableVersion(other.toString());
return this.comparableVersion.compareTo(otherComparable);
}
@Override
public boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots) {
ComparableVersion comparableCandidate = (candidate instanceof AbstractDependencyVersion)
? ((AbstractDependencyVersion) candidate).comparableVersion
: new ComparableVersion(candidate.toString());
return comparableCandidate.compareTo(this.comparableVersion) > 0;
}
@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();
}
}

@ -1,162 +0,0 @@
/*
* Copyright 2012-2023 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.Objects;
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;
import org.springframework.util.StringUtils;
/**
* 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(toNormalizedString(artifactVersion)));
this.artifactVersion = artifactVersion;
}
private static String toNormalizedString(ArtifactVersion artifactVersion) {
String versionString = artifactVersion.toString();
if (versionString.endsWith(".RELEASE")) {
return versionString.substring(0, versionString.length() - 8);
}
if (versionString.endsWith(".BUILD-SNAPSHOT")) {
return versionString.substring(0, versionString.length() - 15) + "-SNAPSHOT";
}
return versionString;
}
protected ArtifactVersionDependencyVersion(ArtifactVersion artifactVersion, ComparableVersion comparableVersion) {
super(comparableVersion);
this.artifactVersion = artifactVersion;
}
@Override
public boolean isSameMajor(DependencyVersion other) {
if (other instanceof ReleaseTrainDependencyVersion) {
return false;
}
return extractArtifactVersionDependencyVersion(other).map(this::isSameMajor).orElse(true);
}
private boolean isSameMajor(ArtifactVersionDependencyVersion other) {
return this.artifactVersion.getMajorVersion() == other.artifactVersion.getMajorVersion();
}
@Override
public boolean isSameMinor(DependencyVersion other) {
if (other instanceof ReleaseTrainDependencyVersion) {
return false;
}
return extractArtifactVersionDependencyVersion(other).map(this::isSameMinor).orElse(true);
}
private boolean isSameMinor(ArtifactVersionDependencyVersion other) {
return isSameMajor(other) && this.artifactVersion.getMinorVersion() == other.artifactVersion.getMinorVersion();
}
@Override
public boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots) {
if (!(candidate instanceof ArtifactVersionDependencyVersion)) {
return false;
}
ArtifactVersion other = ((ArtifactVersionDependencyVersion) candidate).artifactVersion;
if (this.artifactVersion.equals(other)) {
return false;
}
if (sameMajorMinorIncremental(other)) {
if (!StringUtils.hasLength(this.artifactVersion.getQualifier())
|| "RELEASE".equals(this.artifactVersion.getQualifier())) {
return false;
}
if (isSnapshot()) {
return true;
}
else if (((ArtifactVersionDependencyVersion) candidate).isSnapshot()) {
return movingToSnapshots;
}
}
return super.isUpgrade(candidate, movingToSnapshots);
}
private boolean sameMajorMinorIncremental(ArtifactVersion other) {
return this.artifactVersion.getMajorVersion() == other.getMajorVersion()
&& this.artifactVersion.getMinorVersion() == other.getMinorVersion()
&& this.artifactVersion.getIncrementalVersion() == other.getIncrementalVersion();
}
private boolean isSnapshot() {
return "SNAPSHOT".equals(this.artifactVersion.getQualifier())
|| "BUILD".equals(this.artifactVersion.getQualifier());
}
@Override
public boolean isSnapshotFor(DependencyVersion candidate) {
if (!isSnapshot() || !(candidate instanceof ArtifactVersionDependencyVersion)) {
return false;
}
return sameMajorMinorIncremental(((ArtifactVersionDependencyVersion) candidate).artifactVersion);
}
@Override
public int compareTo(DependencyVersion other) {
if (other instanceof ArtifactVersionDependencyVersion otherArtifactDependencyVersion) {
ArtifactVersion otherArtifactVersion = otherArtifactDependencyVersion.artifactVersion;
if ((!Objects.equals(this.artifactVersion.getQualifier(), otherArtifactVersion.getQualifier()))
&& "snapshot".equalsIgnoreCase(otherArtifactVersion.getQualifier())
&& otherArtifactVersion.getMajorVersion() == this.artifactVersion.getMajorVersion()
&& otherArtifactVersion.getMinorVersion() == this.artifactVersion.getMinorVersion()
&& otherArtifactVersion.getIncrementalVersion() == this.artifactVersion.getIncrementalVersion()) {
return 1;
}
}
return super.compareTo(other);
}
@Override
public String toString() {
return this.artifactVersion.toString();
}
protected Optional<ArtifactVersionDependencyVersion> extractArtifactVersionDependencyVersion(
DependencyVersion other) {
ArtifactVersionDependencyVersion artifactVersion = null;
if (other instanceof ArtifactVersionDependencyVersion otherVersion) {
artifactVersion = otherVersion;
}
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);
}
}

@ -1,55 +0,0 @@
/*
* Copyright 2012-2023 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.Pattern;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
/**
* A specialization of {@link ArtifactVersionDependencyVersion} for calendar versions.
* Calendar versions are always considered to be newer than
* {@link ReleaseTrainDependencyVersion release train versions}.
*
* @author Andy Wilkinson
*/
class CalendarVersionDependencyVersion extends ArtifactVersionDependencyVersion {
private static final Pattern CALENDAR_VERSION_PATTERN = Pattern.compile("\\d{4}\\.\\d+\\.\\d+(-.+)?");
protected CalendarVersionDependencyVersion(ArtifactVersion artifactVersion) {
super(artifactVersion);
}
protected CalendarVersionDependencyVersion(ArtifactVersion artifactVersion, ComparableVersion comparableVersion) {
super(artifactVersion, comparableVersion);
}
static CalendarVersionDependencyVersion parse(String version) {
if (!CALENDAR_VERSION_PATTERN.matcher(version).matches()) {
return null;
}
ArtifactVersion artifactVersion = new DefaultArtifactVersion(version);
if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(version)) {
return null;
}
return new CalendarVersionDependencyVersion(artifactVersion);
}
}

@ -1,58 +0,0 @@
/*
* 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);
}
}

@ -1,78 +0,0 @@
/*
* Copyright 2012-2023 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 has the same major and minor versions as the
* {@code other} version.
* @param other the version to test
* @return {@code true} if this version has the same major and minor, otherwise
* {@code false}
*/
boolean isSameMinor(DependencyVersion other);
/**
* Returns whether this version has the same major version as the {@code other}
* version.
* @param other the version to test
* @return {@code true} if this version has the same major, otherwise {@code false}
*/
boolean isSameMajor(DependencyVersion other);
/**
* Returns whether the given {@code candidate} is an upgrade of this version.
* @param candidate the version to consider
* @param movingToSnapshots whether the upgrade is to be considered as part of moving
* to snaphots
* @return {@code true} if the candidate is an upgrade, otherwise false
*/
boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots);
/**
* Returns whether this version is a snapshot for the given {@code candidate}.
* @param candidate the version to consider
* @return {@code true} if this version is a snapshot for the candidate, otherwise
* false
*/
boolean isSnapshotFor(DependencyVersion candidate);
static DependencyVersion parse(String version) {
List<Function<String, DependencyVersion>> parsers = Arrays.asList(CalendarVersionDependencyVersion::parse,
ArtifactVersionDependencyVersion::parse, ReleaseTrainDependencyVersion::parse,
MultipleComponentsDependencyVersion::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");
}
}

@ -1,56 +0,0 @@
/*
* 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);
}
}

@ -1,58 +0,0 @@
/*
* Copyright 2012-2023 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 or five components
* that cannot be handled by {@link ArtifactVersion} because the fourth component is
* numeric.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
*/
final class MultipleComponentsDependencyVersion extends ArtifactVersionDependencyVersion {
private final String original;
private MultipleComponentsDependencyVersion(ArtifactVersion artifactVersion, String original) {
super(artifactVersion, new ComparableVersion(original));
this.original = original;
}
@Override
public String toString() {
return this.original;
}
static MultipleComponentsDependencyVersion parse(String input) {
String[] components = input.split("\\.");
if (components.length == 4 || components.length == 5) {
ArtifactVersion artifactVersion = new DefaultArtifactVersion(
components[0] + "." + components[1] + "." + components[2]);
if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(input)) {
return null;
}
return new MultipleComponentsDependencyVersion(artifactVersion, input);
}
return null;
}
}

@ -1,156 +0,0 @@
/*
* Copyright 2012-2023 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]+)-((BUILD-SNAPSHOT)|([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 otherReleaseTrain)) {
return -1;
}
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 isUpgrade(DependencyVersion candidate, boolean movingToSnapshots) {
if (!(candidate instanceof ReleaseTrainDependencyVersion)) {
return true;
}
ReleaseTrainDependencyVersion candidateReleaseTrain = (ReleaseTrainDependencyVersion) candidate;
int comparison = this.releaseTrain.compareTo(candidateReleaseTrain.releaseTrain);
if (comparison != 0) {
return comparison < 0;
}
if (movingToSnapshots && !isSnapshot() && candidateReleaseTrain.isSnapshot()) {
return true;
}
comparison = this.type.compareTo(candidateReleaseTrain.type);
if (comparison != 0) {
return comparison < 0;
}
return Integer.compare(this.version, candidateReleaseTrain.version) < 0;
}
private boolean isSnapshot() {
return "BUILD-SNAPSHOT".equals(this.type);
}
@Override
public boolean isSnapshotFor(DependencyVersion candidate) {
if (!isSnapshot() || !(candidate instanceof ReleaseTrainDependencyVersion)) {
return false;
}
ReleaseTrainDependencyVersion candidateReleaseTrain = (ReleaseTrainDependencyVersion) candidate;
return this.releaseTrain.equals(candidateReleaseTrain.releaseTrain);
}
@Override
public boolean isSameMajor(DependencyVersion other) {
return isSameReleaseTrain(other);
}
@Override
public boolean isSameMinor(DependencyVersion other) {
return isSameReleaseTrain(other);
}
private boolean isSameReleaseTrain(DependencyVersion other) {
if (other instanceof CalendarVersionDependencyVersion) {
return false;
}
if (other instanceof ReleaseTrainDependencyVersion otherReleaseTrain) {
return otherReleaseTrain.releaseTrain.equals(this.releaseTrain);
}
return true;
}
@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),
StringUtils.hasLength(matcher.group(3)) ? matcher.group(3) : matcher.group(4),
(StringUtils.hasLength(matcher.group(5))) ? Integer.parseInt(matcher.group(5)) : 0, input);
}
}

@ -1,60 +0,0 @@
/*
* Copyright 2012-2023 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 isSameMajor(DependencyVersion other) {
return true;
}
@Override
public boolean isSameMinor(DependencyVersion other) {
return true;
}
@Override
public String toString() {
return this.version;
}
@Override
public boolean isSnapshotFor(DependencyVersion candidate) {
return false;
}
static UnstructuredDependencyVersion parse(String version) {
return new UnstructuredDependencyVersion(version);
}
}

@ -1,140 +0,0 @@
/*
* Copyright 2012-2023 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 java.util.stream.Stream;
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();
try (Stream<Path> pathStream = Files.walk(root)) {
pathStream.filter(Files::isRegularFile)
.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;
}
}
}

@ -1,109 +0,0 @@
/*
* Copyright 2012-2023 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.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() {
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.equals("javax.cache")) {
return false;
}
if (group.equals("javax.money")) {
return false;
}
if (group.equals("org.codehaus.groovy")) {
return true;
}
if (group.equals("org.eclipse.jetty.toolchain")) {
return true;
}
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;
}
if (group.startsWith("org.jboss.spec")) {
return true;
}
if (group.equals("org.apache.geronimo.specs")) {
return true;
}
if (group.equals("com.sun.activation")) {
return true;
}
return false;
}
}

@ -1,78 +0,0 @@
/*
* Copyright 2023-2023 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.util.Set;
import java.util.stream.Collectors;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.component.ModuleComponentSelector;
import org.gradle.api.artifacts.result.DependencyResult;
import org.gradle.api.artifacts.result.ResolutionResult;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.TaskAction;
/**
* Tasks to check that none of classpath's direct dependencies are unconstrained.
*
* @author Andy Wilkinson
*/
public class CheckClasspathForUnconstrainedDirectDependencies extends DefaultTask {
private Configuration classpath;
public CheckClasspathForUnconstrainedDirectDependencies() {
getOutputs().upToDateWhen((task) -> true);
}
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
public void setClasspath(Configuration classpath) {
this.classpath = classpath;
}
@TaskAction
void checkForUnconstrainedDirectDependencies() {
ResolutionResult resolutionResult = this.classpath.getIncoming().getResolutionResult();
Set<? extends DependencyResult> dependencies = resolutionResult.getRoot().getDependencies();
Set<String> unconstrainedDependencies = dependencies.stream()
.map(DependencyResult::getRequested)
.filter(ModuleComponentSelector.class::isInstance)
.map(ModuleComponentSelector.class::cast)
.map((selector) -> selector.getGroup() + ":" + selector.getModule())
.collect(Collectors.toSet());
Set<String> constraints = resolutionResult.getAllDependencies()
.stream()
.filter(DependencyResult::isConstraint)
.map(DependencyResult::getRequested)
.filter(ModuleComponentSelector.class::isInstance)
.map(ModuleComponentSelector.class::cast)
.map((selector) -> selector.getGroup() + ":" + selector.getModule())
.collect(Collectors.toSet());
unconstrainedDependencies.removeAll(constraints);
if (!unconstrainedDependencies.isEmpty()) {
throw new GradleException("Found unconstrained direct dependencies: " + unconstrainedDependencies);
}
}
}

@ -1,170 +0,0 @@
/*
* Copyright 2012-2023 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.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.inject.Inject;
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.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ExcludeRule;
import org.gradle.api.artifacts.ModuleDependency;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
/**
* A {@link Task} for checking the classpath for unnecessary exclusions.
*
* @author Andy Wilkinson
*/
public class CheckClasspathForUnnecessaryExclusions extends DefaultTask {
private static final Map<String, String> SPRING_BOOT_DEPENDENCIES_PROJECT = Collections.singletonMap("path",
":spring-boot-project:spring-boot-dependencies");
private final Map<String, Set<String>> exclusionsByDependencyId = new TreeMap<>();
private final Map<String, Dependency> dependencyById = new HashMap<>();
private final Dependency platform;
private final DependencyHandler dependencyHandler;
private final ConfigurationContainer configurations;
private Configuration classpath;
@Inject
public CheckClasspathForUnnecessaryExclusions(DependencyHandler dependencyHandler,
ConfigurationContainer configurations) {
this.dependencyHandler = getProject().getDependencies();
this.configurations = getProject().getConfigurations();
this.platform = this.dependencyHandler
.create(this.dependencyHandler.platform(this.dependencyHandler.project(SPRING_BOOT_DEPENDENCIES_PROJECT)));
getOutputs().upToDateWhen((task) -> true);
}
public void setClasspath(Configuration classpath) {
this.classpath = classpath;
this.exclusionsByDependencyId.clear();
this.dependencyById.clear();
classpath.getAllDependencies().all(this::processDependency);
}
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
private void processDependency(Dependency dependency) {
if (dependency instanceof ModuleDependency moduleDependency) {
processDependency(moduleDependency);
}
}
private void processDependency(ModuleDependency dependency) {
String dependencyId = getId(dependency);
TreeSet<String> exclusions = dependency.getExcludeRules()
.stream()
.map(this::getId)
.collect(Collectors.toCollection(TreeSet::new));
this.exclusionsByDependencyId.put(dependencyId, exclusions);
if (!exclusions.isEmpty()) {
this.dependencyById.put(dependencyId, getProject().getDependencies().create(dependencyId));
}
}
@Input
Map<String, Set<String>> getExclusionsByDependencyId() {
return this.exclusionsByDependencyId;
}
@TaskAction
public void checkForUnnecessaryExclusions() {
Map<String, Set<String>> unnecessaryExclusions = new HashMap<>();
this.exclusionsByDependencyId.forEach((dependencyId, exclusions) -> {
if (!exclusions.isEmpty()) {
Dependency toCheck = this.dependencyById.get(dependencyId);
this.configurations.detachedConfiguration(toCheck, this.platform)
.getIncoming()
.getArtifacts()
.getArtifacts()
.stream()
.map(this::getId)
.forEach(exclusions::remove);
removeProfileExclusions(dependencyId, exclusions);
if (!exclusions.isEmpty()) {
unnecessaryExclusions.put(dependencyId, exclusions);
}
}
});
if (!unnecessaryExclusions.isEmpty()) {
throw new GradleException(getExceptionMessage(unnecessaryExclusions));
}
}
private void removeProfileExclusions(String dependencyId, Set<String> exclusions) {
if ("org.xmlunit:xmlunit-core".equals(dependencyId)) {
exclusions.remove("javax.xml.bind:jaxb-api");
}
}
private String getExceptionMessage(Map<String, Set<String>> unnecessaryExclusions) {
StringBuilder message = new StringBuilder("Unnecessary exclusions detected:");
for (Entry<String, Set<String>> entry : unnecessaryExclusions.entrySet()) {
message.append(String.format("%n %s", entry.getKey()));
for (String exclusion : entry.getValue()) {
message.append(String.format("%n %s", exclusion));
}
}
return message.toString();
}
private String getId(ResolvedArtifactResult artifact) {
return getId((ModuleComponentIdentifier) artifact.getId().getComponentIdentifier());
}
private String getId(ModuleDependency dependency) {
return dependency.getGroup() + ":" + dependency.getName();
}
private String getId(ExcludeRule rule) {
return rule.getGroup() + ":" + rule.getModule();
}
private String getId(ModuleComponentIdentifier identifier) {
return identifier.getGroup() + ":" + identifier.getModule();
}
}

@ -1,118 +0,0 @@
/*
* Copyright 2012-2023 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.Collections;
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.Task;
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.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskExecutionException;
import org.springframework.boot.build.artifacts.ArtifactRelease;
/**
* A {@link Task} for creating a Homebrew formula manifest.
*
* @author Andy Wilkinson
*/
public class HomebrewFormula extends DefaultTask {
private Provider<RegularFile> archive;
private File template;
private File outputDir;
public HomebrewFormula() {
getInputs().property("version", getProject().provider(getProject()::getVersion));
}
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
public RegularFile getArchive() {
return this.archive.get();
}
public void setArchive(Provider<RegularFile> archive) {
this.archive = archive;
}
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
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);
copy.expand(getProperties(additionalProperties));
});
}
private Map<String, Object> getProperties(Map<String, Object> additionalProperties) {
Map<String, Object> properties = new HashMap<>(additionalProperties);
Project project = getProject();
properties.put("hash", sha256(this.archive.get().getAsFile()));
properties.put("repo", ArtifactRelease.forProject(project).getDownloadRepo());
properties.put("project", project);
return 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);
}
}
@TaskAction
void createFormula() {
createDescriptor(Collections.emptyMap());
}
}

@ -1,81 +0,0 @@
/*
* Copyright 2012-2021 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("|===");
}
}
}

@ -1,80 +0,0 @@
/*
* Copyright 2012-2021 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.VersionProperty;
/**
* Task for documenting available version properties.
*
* @author Christoph Dreis
*/
public class DocumentVersionProperties extends DefaultTask {
private final SetProperty<VersionProperty> versionProperties;
private File outputFile;
@Inject
public DocumentVersionProperties(ObjectFactory objectFactory) {
this.versionProperties = objectFactory.setProperty(VersionProperty.class);
}
@Input
public SetProperty<VersionProperty> getVersionProperties() {
return this.versionProperties;
}
@OutputFile
public File getOutputFile() {
return this.outputFile;
}
public void setOutputFile(File outputFile) {
this.outputFile = outputFile;
}
@TaskAction
public void documentVersionProperties() throws IOException {
this.outputFile.getParentFile().mkdirs();
try (PrintWriter writer = new PrintWriter(new FileWriter(this.outputFile))) {
writer.println("|===");
writer.println("| Library | Version Property");
for (VersionProperty versionProperty : this.versionProperties.get()) {
writer.println();
writer.printf("| `%s`%n", versionProperty.getLibraryName());
writer.printf("| `%s`%n", versionProperty.getVersionProperty());
}
writer.println("|===");
}
}
}

@ -1,195 +0,0 @@
/*
* Copyright 2012-2023 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.DefaultTask;
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.tasks.Internal;
import org.gradle.api.tasks.TaskAction;
import org.gradle.platform.base.Platform;
import org.springframework.boot.build.bom.BomExtension;
import org.springframework.boot.build.bom.Library;
/**
* {@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 DefaultTask {
private final Configuration configuration;
private final Map<String, String> versionConstraints = new TreeMap<>();
private final Set<ConstrainedVersion> constrainedVersions = new TreeSet<>();
private final Set<VersionProperty> versionProperties = new TreeSet<>();
private final List<String> projectPaths = new ArrayList<>();
public ExtractVersionConstraints() {
DependencyHandler dependencies = getProject().getDependencies();
this.configuration = getProject().getConfigurations().create(getName());
dependencies.getComponents().all(this::processMetadataDetails);
}
public void enforcedPlatform(String projectPath) {
this.configuration.getDependencies()
.add(getProject().getDependencies()
.enforcedPlatform(
getProject().getDependencies().project(Collections.singletonMap("path", projectPath))));
this.projectPaths.add(projectPath);
}
@Internal
public Map<String, String> getVersionConstraints() {
return Collections.unmodifiableMap(this.versionConstraints);
}
@Internal
public Set<ConstrainedVersion> getConstrainedVersions() {
return this.constrainedVersions;
}
@Internal
public Set<VersionProperty> getVersionProperties() {
return this.versionProperties;
}
@TaskAction
void extractVersionConstraints() {
this.configuration.resolve();
for (String projectPath : this.projectPaths) {
extractVersionProperties(projectPath);
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 extractVersionProperties(String projectPath) {
Object bom = getProject().project(projectPath).getExtensions().getByName("bom");
BomExtension bomExtension = (BomExtension) bom;
for (Library lib : bomExtension.getLibraries()) {
String versionProperty = lib.getVersionProperty();
if (versionProperty != null) {
this.versionProperties.add(new VersionProperty(lib.getName(), versionProperty));
}
}
}
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);
}
}
public static final class VersionProperty implements Comparable<VersionProperty>, Serializable {
private final String libraryName;
private final String versionProperty;
public VersionProperty(String libraryName, String versionProperty) {
this.libraryName = libraryName;
this.versionProperty = versionProperty;
}
public String getLibraryName() {
return this.libraryName;
}
public String getVersionProperty() {
return this.versionProperty;
}
@Override
public int compareTo(VersionProperty other) {
int groupComparison = this.libraryName.compareToIgnoreCase(other.libraryName);
if (groupComparison != 0) {
return groupComparison;
}
return this.versionProperty.compareTo(other.versionProperty);
}
}
}

@ -1,59 +0,0 @@
/*
* Copyright 2012-2021 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 Asciidoc {
private final StringBuilder content;
Asciidoc() {
this.content = new StringBuilder();
}
Asciidoc appendWithHardLineBreaks(Object... items) {
for (Object item : items) {
appendln("`+", item, "+` +");
}
return this;
}
Asciidoc appendln(Object... items) {
return append(items).newLine();
}
Asciidoc append(Object... items) {
for (Object item : items) {
this.content.append(item);
}
return this;
}
Asciidoc newLine() {
return append(System.lineSeparator());
}
@Override
public String toString() {
return this.content.toString();
}
}

@ -1,165 +0,0 @@
/*
* Copyright 2012-2022 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 java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.gradle.api.GradleException;
import org.gradle.api.file.FileTree;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskAction;
/**
* {@link SourceTask} that checks additional Spring configuration metadata files.
*
* @author Andy Wilkinson
*/
public class CheckAdditionalSpringConfigurationMetadata extends SourceTask {
private final RegularFileProperty reportLocation;
public CheckAdditionalSpringConfigurationMetadata() {
this.reportLocation = getProject().getObjects().fileProperty();
}
@OutputFile
public RegularFileProperty getReportLocation() {
return this.reportLocation;
}
@Override
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
public FileTree getSource() {
return super.getSource();
}
@TaskAction
void check() throws JsonParseException, IOException {
Report report = createReport();
File reportFile = getReportLocation().get().getAsFile();
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
if (report.hasProblems()) {
throw new GradleException(
"Problems found in additional Spring configuration metadata. See " + reportFile + " for details.");
}
}
@SuppressWarnings("unchecked")
private Report createReport() throws IOException, JsonParseException, JsonMappingException {
ObjectMapper objectMapper = new ObjectMapper();
Report report = new Report();
for (File file : getSource().getFiles()) {
Analysis analysis = report.analysis(getProject().getProjectDir().toPath().relativize(file.toPath()));
Map<String, Object> json = objectMapper.readValue(file, Map.class);
check("groups", json, analysis);
check("properties", json, analysis);
check("hints", json, analysis);
}
return report;
}
@SuppressWarnings("unchecked")
private void check(String key, Map<String, Object> json, Analysis analysis) {
List<Map<String, Object>> groups = (List<Map<String, Object>>) json.get(key);
List<String> names = groups.stream().map((group) -> (String) group.get("name")).toList();
List<String> sortedNames = sortedCopy(names);
for (int i = 0; i < names.size(); i++) {
String actual = names.get(i);
String expected = sortedNames.get(i);
if (!actual.equals(expected)) {
analysis.problems.add("Wrong order at $." + key + "[" + i + "].name - expected '" + expected
+ "' but found '" + actual + "'");
}
}
}
private List<String> sortedCopy(Collection<String> original) {
List<String> copy = new ArrayList<>(original);
Collections.sort(copy);
return copy;
}
private static final class Report implements Iterable<String> {
private final List<Analysis> analyses = new ArrayList<>();
private Analysis analysis(Path path) {
Analysis analysis = new Analysis(path);
this.analyses.add(analysis);
return analysis;
}
private boolean hasProblems() {
for (Analysis analysis : this.analyses) {
if (!analysis.problems.isEmpty()) {
return true;
}
}
return false;
}
@Override
public Iterator<String> iterator() {
List<String> lines = new ArrayList<>();
for (Analysis analysis : this.analyses) {
lines.add(analysis.source.toString());
lines.add("");
if (analysis.problems.isEmpty()) {
lines.add("No problems found.");
}
else {
lines.addAll(analysis.problems);
}
lines.add("");
}
return lines.iterator();
}
}
private static final class Analysis {
private final List<String> problems = new ArrayList<>();
private final Path source;
private Analysis(Path source) {
this.source = source;
}
}
}

@ -1,165 +0,0 @@
/*
* Copyright 2012-2023 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 java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskAction;
/**
* {@link SourceTask} that checks {@code spring-configuration-metadata.json} files.
*
* @author Andy Wilkinson
*/
public class CheckSpringConfigurationMetadata extends DefaultTask {
private List<String> exclusions = new ArrayList<>();
private final RegularFileProperty reportLocation;
private final RegularFileProperty metadataLocation;
public CheckSpringConfigurationMetadata() {
this.metadataLocation = getProject().getObjects().fileProperty();
this.reportLocation = getProject().getObjects().fileProperty();
}
@OutputFile
public RegularFileProperty getReportLocation() {
return this.reportLocation;
}
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
public RegularFileProperty getMetadataLocation() {
return this.metadataLocation;
}
public void setExclusions(List<String> exclusions) {
this.exclusions = exclusions;
}
@Input
public List<String> getExclusions() {
return this.exclusions;
}
@TaskAction
void check() throws JsonParseException, IOException {
Report report = createReport();
File reportFile = getReportLocation().get().getAsFile();
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
if (report.hasProblems()) {
throw new GradleException(
"Problems found in Spring configuration metadata. See " + reportFile + " for details.");
}
}
@SuppressWarnings("unchecked")
private Report createReport() throws IOException, JsonParseException, JsonMappingException {
ObjectMapper objectMapper = new ObjectMapper();
File file = this.metadataLocation.get().getAsFile();
Report report = new Report(getProject().getProjectDir().toPath().relativize(file.toPath()));
Map<String, Object> json = objectMapper.readValue(file, Map.class);
List<Map<String, Object>> properties = (List<Map<String, Object>>) json.get("properties");
for (Map<String, Object> property : properties) {
String name = (String) property.get("name");
if (!isDeprecated(property) && !isDescribed(property) && !isExcluded(name)) {
report.propertiesWithNoDescription.add(name);
}
}
return report;
}
private boolean isExcluded(String propertyName) {
for (String exclusion : this.exclusions) {
if (propertyName.equals(exclusion)) {
return true;
}
if (exclusion.endsWith(".*")) {
if (propertyName.startsWith(exclusion.substring(0, exclusion.length() - 2))) {
return true;
}
}
}
return false;
}
@SuppressWarnings("unchecked")
private boolean isDeprecated(Map<String, Object> property) {
return (Map<String, Object>) property.get("deprecation") != null;
}
private boolean isDescribed(Map<String, Object> property) {
return property.get("description") != null;
}
private static final class Report implements Iterable<String> {
private final List<String> propertiesWithNoDescription = new ArrayList<>();
private final Path source;
private Report(Path source) {
this.source = source;
}
private boolean hasProblems() {
return !this.propertiesWithNoDescription.isEmpty();
}
@Override
public Iterator<String> iterator() {
List<String> lines = new ArrayList<>();
lines.add(this.source.toString());
lines.add("");
if (this.propertiesWithNoDescription.isEmpty()) {
lines.add("No problems found.");
}
else {
lines.add("The following properties have no description:");
lines.add("");
lines.addAll(this.propertiesWithNoDescription.stream().map((line) -> "\t" + line).toList());
}
lines.add("");
return lines.iterator();
}
}
}

@ -1,55 +0,0 @@
/*
* Copyright 2012-2021 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;
/**
* Table row regrouping a list of configuration properties sharing the same description.
*
* @author Brian Clozel
* @author Phillip Webb
*/
class CompoundRow extends Row {
private final Set<String> propertyNames;
private final String description;
CompoundRow(Snippet snippet, String prefix, String description) {
super(snippet, prefix);
this.description = description;
this.propertyNames = new TreeSet<>();
}
void addProperty(ConfigurationProperty property) {
this.propertyNames.add(property.getDisplayName());
}
@Override
void write(Asciidoc asciidoc) {
asciidoc.append("|");
asciidoc.append("[[" + getAnchor() + "]]");
asciidoc.append("<<" + getAnchor() + ",");
this.propertyNames.forEach(asciidoc::appendWithHardLineBreaks);
asciidoc.appendln(">>");
asciidoc.appendln("|+++", this.description, "+++");
asciidoc.appendln("|");
}
}

@ -1,75 +0,0 @@
/*
* Copyright 2012-2021 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 java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Configuration properties read from one or more
* {@code META-INF/spring-configuration-metadata.json} files.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
final class ConfigurationProperties {
private final Map<String, ConfigurationProperty> byName;
private ConfigurationProperties(List<ConfigurationProperty> properties) {
Map<String, ConfigurationProperty> byName = new LinkedHashMap<>();
for (ConfigurationProperty property : properties) {
byName.put(property.getName(), property);
}
this.byName = Collections.unmodifiableMap(byName);
}
ConfigurationProperty get(String propertyName) {
return this.byName.get(propertyName);
}
Stream<ConfigurationProperty> stream() {
return this.byName.values().stream();
}
@SuppressWarnings("unchecked")
static ConfigurationProperties fromFiles(Iterable<File> files) {
try {
ObjectMapper objectMapper = new ObjectMapper();
List<ConfigurationProperty> properties = new ArrayList<>();
for (File file : files) {
Map<String, Object> json = objectMapper.readValue(file, Map.class);
for (Map<String, Object> property : (List<Map<String, Object>>) json.get("properties")) {
properties.add(ConfigurationProperty.fromJsonProperties(property));
}
}
return new ConfigurationProperties(properties);
}
catch (IOException ex) {
throw new RuntimeException("Failed to load configuration metadata", ex);
}
}
}

@ -1,188 +0,0 @@
/*
* Copyright 2012-2023 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.Collections;
import java.util.stream.Collectors;
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.file.RegularFile;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.springframework.boot.build.processors.ProcessedAnnotationsPlugin;
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>Disables incremental compilation to avoid property descriptions being lost.
* <li>Configuring the additional metadata locations annotation processor compiler
* argument.
* <li>Adding the outputs of the processResources task as inputs of the compileJava task
* to ensure that the additional metadata is available when the annotation processor runs.
* <li>Registering a {@link CheckAdditionalSpringConfigurationMetadata} task and
* configuring the {@code check} task to depend upon it.
* <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";
/**
* Name of the {@link CheckAdditionalSpringConfigurationMetadata} task.
*/
public static final String CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkAdditionalSpringConfigurationMetadata";
/**
* Name of the {@link CheckAdditionalSpringConfigurationMetadata} task.
*/
public static final String CHECK_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkSpringConfigurationMetadata";
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
configureConfigurationPropertiesAnnotationProcessor(project);
disableIncrementalCompilation(project);
configureAdditionalMetadataLocationsCompilerArgument(project);
registerCheckAdditionalMetadataTask(project);
registerCheckMetadataTask(project);
addMetadataArtifact(project);
});
}
private void configureConfigurationPropertiesAnnotationProcessor(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")));
project.getPlugins().apply(ProcessedAnnotationsPlugin.class);
}
private void disableIncrementalCompilation(Project project) {
SourceSet mainSourceSet = project.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
project.getTasks()
.named(mainSourceSet.getCompileJavaTaskName(), JavaCompile.class)
.configure((compileJava) -> compileJava.getOptions().setIncremental(false));
}
private void addMetadataArtifact(Project project) {
SourceSet mainSourceSet = project.getExtensions()
.getByType(JavaPluginExtension.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,
mainSourceSet.getJava()
.getDestinationDirectory()
.dir("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);
((Task) compileJava).getInputs()
.files(project.getTasks().getByName(JavaPlugin.PROCESS_RESOURCES_TASK_NAME))
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("processed resources");
SourceSet mainSourceSet = project.getExtensions()
.getByType(JavaPluginExtension.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())));
}
private void registerCheckAdditionalMetadataTask(Project project) {
TaskProvider<CheckAdditionalSpringConfigurationMetadata> checkConfigurationMetadata = project.getTasks()
.register(CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME,
CheckAdditionalSpringConfigurationMetadata.class);
checkConfigurationMetadata.configure((check) -> {
SourceSet mainSourceSet = project.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
check.setSource(mainSourceSet.getResources());
check.include("META-INF/additional-spring-configuration-metadata.json");
check.getReportLocation()
.set(project.getLayout()
.getBuildDirectory()
.file("reports/additional-spring-configuration-metadata/check.txt"));
});
project.getTasks()
.named(LifecycleBasePlugin.CHECK_TASK_NAME)
.configure((check) -> check.dependsOn(checkConfigurationMetadata));
}
private void registerCheckMetadataTask(Project project) {
TaskProvider<CheckSpringConfigurationMetadata> checkConfigurationMetadata = project.getTasks()
.register(CHECK_SPRING_CONFIGURATION_METADATA_TASK_NAME, CheckSpringConfigurationMetadata.class);
checkConfigurationMetadata.configure((check) -> {
SourceSet mainSourceSet = project.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
Provider<RegularFile> metadataLocation = project.getTasks()
.named(mainSourceSet.getCompileJavaTaskName(), JavaCompile.class)
.flatMap((javaCompile) -> javaCompile.getDestinationDirectory()
.file("META-INF/spring-configuration-metadata.json"));
check.getMetadataLocation().set(metadataLocation);
check.getReportLocation()
.set(project.getLayout().getBuildDirectory().file("reports/spring-configuration-metadata/check.txt"));
});
project.getTasks()
.named(LifecycleBasePlugin.CHECK_TASK_NAME)
.configure((check) -> check.dependsOn(checkConfigurationMetadata));
}
}

@ -1,88 +0,0 @@
/*
* Copyright 2012-2021 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.Map;
/**
* A configuration property.
*
* @author Andy Wilkinson
*/
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;
}
String getName() {
return this.name;
}
String getDisplayName() {
return (getType() != null && getType().startsWith("java.util.Map")) ? getName() + ".*" : getName();
}
String getType() {
return this.type;
}
Object getDefaultValue() {
return this.defaultValue;
}
String getDescription() {
return this.description;
}
boolean isDeprecated() {
return this.deprecated;
}
@Override
public String toString() {
return "ConfigurationProperty [name=" + this.name + ", type=" + this.type + "]";
}
static ConfigurationProperty fromJsonProperties(Map<String, Object> property) {
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");
return new ConfigurationProperty(name, type, defaultValue, description, deprecated);
}
}

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

Loading…
Cancel
Save