Compare commits

..

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

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

@ -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 +1,10 @@
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: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1

1
.gitignore vendored

@ -12,6 +12,7 @@
.classpath
.factorypath
.gradle
.idea
.metadata
.project
.recommenders

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>

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

@ -0,0 +1 @@
java-baseline=8

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

@ -6,7 +6,7 @@ Spring Boot is released under the Apache 2.0 license. If you would like to contr
== 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.
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.

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

@ -1,4 +1,4 @@
= 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.5.x/jobs/build/badge["Build Status", link="https://ci.spring.io/teams/spring-boot/pipelines/spring-boot-2.5.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"]
:docs: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference
:github: https://github.com/spring-projects/spring-boot
@ -10,7 +10,6 @@ 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).
@ -76,7 +75,7 @@ We like to know the Spring Boot version, operating system, and JVM version you'r
== 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 also need JDK 1.8.
[indent=0]
----
@ -126,6 +125,12 @@ For example, if you want to get started using Spring and JPA for database access
=== spring-boot-cli
The Spring command-line application compiles and runs Groovy source, allowing you to write the absolute minimum amount 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.
@ -165,6 +170,12 @@ Developer tools are automatically disabled when running a fully packaged applica
== 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 the samples directory.
== Guides
The https://spring.io/[spring.io] site contains several guides that show how to use Spring Boot step-by-step:

@ -1,13 +1,11 @@
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"
id "io.spring.nohttp" version "0.0.10"
}
description = "Spring Boot Build"
defaultTasks 'build'
nohttp {
@ -16,9 +14,6 @@ nohttp {
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"
}
@ -43,7 +38,3 @@ allprojects {
resolutionStrategy.cacheChangingModulesFor 0, "minutes"
}
}
tasks.named("checkstyleNohttp").configure {
maxHeapSize = "1536m"
}

@ -2,64 +2,40 @@ plugins {
id "java-gradle-plugin"
id "io.spring.javaformat" version "${javaFormatVersion}"
id "checkstyle"
id "eclipse"
}
repositories {
mavenCentral()
gradlePluginPortal()
maven { url "https://repo.spring.io/release" }
}
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" }
}
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
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(platform("org.springframework:spring-framework-bom:5.3.15"))
implementation("com.fasterxml.jackson.core:jackson-databind:2.11.4")
implementation("commons-codec:commons-codec:1.13")
implementation("org.apache.maven:maven-embedder:3.6.2")
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.gradle:test-retry-gradle-plugin:1.4.0")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.0")
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.0")
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")
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}")
testImplementation("org.assertj:assertj-core:3.11.1")
testImplementation("org.apache.logging.log4j:log4j-core:2.17.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
checkstyle {
toolVersion = "10.12.4"
def archive = configurations.checkstyle.filter { it.name.startsWith("spring-javaformat-checkstyle")}
config = resources.text.fromArchiveEntry(archive, "io/spring/javaformat/checkstyle/checkstyle.xml")
toolVersion = 8.11
}
gradlePlugin {
@ -68,10 +44,6 @@ gradlePlugin {
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"
@ -96,10 +68,6 @@ gradlePlugin {
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"
@ -112,10 +80,6 @@ gradlePlugin {
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"
@ -130,10 +94,3 @@ gradlePlugin {
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 +1 @@
javaFormatVersion=0.0.38
javaFormatVersion=0.0.31

@ -3,4 +3,11 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == "io.spring.javaformat") {
useModule "io.spring.javaformat:spring-javaformat-gradle-plugin:${requested.version}"
}
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -17,6 +17,7 @@
package org.springframework.boot.build;
import java.io.File;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -25,7 +26,6 @@ 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;
@ -38,6 +38,13 @@ import org.springframework.util.StringUtils;
* the plugin is applied:
*
* <ul>
* <li>The {@code https://repo.spring.io/release} Maven repository is configured and
* limited to dependencies in the following groups:
* <ul>
* <li>{@code io.spring.asciidoctor}
* <li>{@code io.spring.asciidoctor.backends}
* <li>{@code io.spring.docresources}
* </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.
@ -69,12 +76,23 @@ class AsciidoctorConventions {
void apply(Project project) {
project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> {
configureDocumentationDependenciesRepository(project);
makeAllWarningsFatal(project);
upgradeAsciidoctorJVersion(project);
createAsciidoctorExtensionsConfiguration(project);
project.getTasks()
.withType(AbstractAsciidoctorTask.class,
(asciidoctorTask) -> configureAsciidoctorTask(project, asciidoctorTask));
project.getTasks().withType(AbstractAsciidoctorTask.class,
(asciidoctorTask) -> configureAsciidoctorTask(project, asciidoctorTask));
});
}
private void configureDocumentationDependenciesRepository(Project project) {
project.getRepositories().maven((mavenRepo) -> {
mavenRepo.setUrl(URI.create("https://repo.spring.io/release"));
mavenRepo.mavenContent((mavenContent) -> {
mavenContent.includeGroup("io.spring.asciidoctor");
mavenContent.includeGroup("io.spring.asciidoctor.backends");
mavenContent.includeGroup("io.spring.docresources");
});
});
}
@ -88,14 +106,12 @@ class AsciidoctorConventions {
private void createAsciidoctorExtensionsConfiguration(Project project) {
project.getConfigurations().create(EXTENSIONS_CONFIGURATION_NAME, (configuration) -> {
project.getConfigurations()
.matching((candidate) -> "dependencyManagement".equals(candidate.getName()))
.all(configuration::extendsFrom);
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.3"));
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"));
.add(project.getDependencies().create("org.asciidoctor:asciidoctorj-pdf:1.5.3"));
});
}
@ -103,13 +119,12 @@ class AsciidoctorConventions {
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");
if (asciidoctorTask instanceof AsciidoctorTask) {
boolean pdf = asciidoctorTask.getName().toLowerCase().contains("pdf");
String backend = (!pdf) ? "spring-html" : "spring-pdf";
task.outputOptions((outputOptions) -> outputOptions.backends(backend));
((AsciidoctorTask) asciidoctorTask).outputOptions((outputOptions) -> outputOptions.backends(backend));
}
}
@ -124,17 +139,9 @@ class AsciidoctorConventions {
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;
return (version.endsWith("-SNAPSHOT")) ? "2.5.x" : version;
}
private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) {
@ -143,15 +150,13 @@ class AsciidoctorConventions {
private Sync createSyncDocumentationSourceTask(Project project, AbstractAsciidoctorTask asciidoctorTask) {
Sync syncDocumentationSource = project.getTasks()
.create("syncDocumentationSourceFor" + StringUtils.capitalize(asciidoctorTask.getName()), Sync.class);
.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.getInputs().dir(syncedSource).withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("synced source");
asciidoctorTask.setSourceDir(project.relativePath(new File(syncedSource, "asciidoc/")));
return syncDocumentationSource;
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -47,8 +47,6 @@ public class ConventionsPlugin implements Plugin<Project> {
new MavenPublishingConventions().apply(project);
new AsciidoctorConventions().apply(project);
new KotlinConventions().apply(project);
new WarConventions().apply(project);
new EclipseConventions().apply(project);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -38,7 +38,6 @@ public class DeployedPlugin implements Plugin<Project> {
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);
@ -46,16 +45,12 @@ public class DeployedPlugin implements Plugin<Project> {
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.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));
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));
}
}

@ -28,7 +28,6 @@ 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;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -24,8 +24,6 @@ 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;
@ -47,9 +45,9 @@ 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.gradle.testretry.TestRetryPlugin;
import org.gradle.testretry.TestRetryTaskExtension;
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;
@ -61,27 +59,24 @@ import org.springframework.util.StringUtils;
* plugin is applied:
*
* <ul>
* <li>The project is configured with source and target compatibility of 17
* <li>The project is configered with source and target compatibility of 1.8
* <li>{@link SpringJavaFormatPlugin Spring Java Format}, {@link CheckstylePlugin
* Checkstyle}, {@link TestFailuresPlugin Test Failures}, and {@link ArchitecturePlugin
* Architecture} plugins are applied
* Checkstyle}, {@link TestFailuresPlugin Test Failures}, and {@link TestRetryPlugin Test
* Retry} 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:
* <li>{@link JavaCompile} tasks are configured to use {@code -parameters}.
* <li>When building with Java 8, {@link JavaCompile} tasks are also configured to:
* <ul>
* <li>Use {@code -parameters}.
* <li>Treat warnings as errors
* <li>Enable {@code unchecked}, {@code deprecation}, {@code rawtypes}, and {@code varags}
* warnings
@ -107,12 +102,11 @@ import org.springframework.util.StringUtils;
*/
class JavaConventions {
private static final String SOURCE_AND_TARGET_COMPATIBILITY = "17";
private static final String SOURCE_AND_TARGET_COMPATIBILITY = "1.8";
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);
@ -125,18 +119,16 @@ class JavaConventions {
}
private void configureJarManifestConventions(Project project) {
ExtractResources extractLegalResources = project.getTasks()
.create("extractLegalResources", ExtractResources.class);
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());
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) -> {
@ -169,43 +161,24 @@ class JavaConventions {
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()
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);
project.getPlugins().apply(TestRetryPlugin.class);
project.getTasks().withType(Test.class,
(test) -> project.getPlugins().withType(TestRetryPlugin.class, (testRetryPlugin) -> {
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
testRetry.getFailOnPassedAfterRetry().set(true);
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");
});
project.getTasks().withType(Javadoc.class, (javadoc) -> javadoc.getOptions().source("1.8").encoding("UTF-8"));
}
private void configureJavaConventions(Project project) {
@ -223,15 +196,15 @@ class JavaConventions {
compile.setSourceCompatibility(SOURCE_AND_TARGET_COMPATIBILITY);
compile.setTargetCompatibility(SOURCE_AND_TARGET_COMPATIBILITY);
}
else if (buildingWithJava17(project)) {
else if (buildingWithJava8(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 boolean buildingWithJava8(Project project) {
return !project.hasProperty("toolchainVersion") && JavaVersion.current() == JavaVersion.VERSION_1_8;
}
private void configureSpringJavaFormat(Project project) {
@ -244,7 +217,7 @@ class JavaConventions {
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));
.add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
}
private void configureDependencyManagement(Project project) {
@ -255,18 +228,14 @@ class JavaConventions {
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()
.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));
project.getPlugins().withType(OptionalDependenciesPlugin.class, (optionalDependencies) -> configurations
.getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME).extendsFrom(dependencyManagement));
}
private void configureToolchain(Project project) {
@ -288,9 +257,9 @@ class JavaConventions {
}
private void createProhibitedDependenciesCheck(Configuration classpath, Project project) {
CheckClasspathForProhibitedDependencies checkClasspathForProhibitedDependencies = project.getTasks()
.create("check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies"),
CheckClasspathForProhibitedDependencies.class);
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,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -30,8 +30,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
* <ul>
* <li>{@link KotlinCompile} tasks are configured to:
* <ul>
* <li>Use {@code apiVersion} and {@code languageVersion} 1.7.
* <li>Use {@code jvmTarget} 17.
* <li>Use {@code apiVersion} and {@code languageVersion} 1.3.
* <li>Use {@code jvmTarget} 1.8.
* <li>Treat all warnings as errors
* <li>Suppress version warnings
* </ul>
@ -44,20 +44,18 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
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);
project.getPlugins().withId("org.jetbrains.kotlin.jvm", (plugin) -> {
project.getTasks().withType(KotlinCompile.class, (compile) -> {
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
kotlinOptions.setApiVersion("1.3");
kotlinOptions.setLanguageVersion("1.3");
kotlinOptions.setJvmTarget("1.8");
kotlinOptions.setAllWarningsAsErrors(true);
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
freeCompilerArgs.add("-Xsuppress-version-warnings");
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
});
});
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -22,6 +22,7 @@ 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.JavaPluginConvention;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.VariantVersionMappingStrategy;
@ -67,9 +68,8 @@ class MavenPublishingConventions {
mavenRepository.setName("deployment");
});
}
publishing.getPublications()
.withType(MavenPublication.class)
.all((mavenPublication) -> customizeMavenPublication(mavenPublication, project));
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();
@ -80,9 +80,8 @@ class MavenPublishingConventions {
private void customizeMavenPublication(MavenPublication publication, Project project) {
customizePom(publication.getPom(), project);
project.getPlugins()
.withType(JavaPlugin.class)
.all((javaPlugin) -> customizeJavaMavenPublication(publication, project));
project.getPlugins().withType(JavaPlugin.class)
.all((javaPlugin) -> customizeJavaMavenPublication(publication, project));
suppressMavenOptionalFeatureWarnings(publication);
}
@ -104,7 +103,7 @@ class MavenPublishingConventions {
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)));
.fromResolutionOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)));
publication.versionMapping(
(strategy) -> strategy.usage(Usage.JAVA_RUNTIME, VariantVersionMappingStrategy::fromResolutionResult));
}
@ -116,10 +115,11 @@ class MavenPublishingConventions {
*/
private void addMavenOptionalFeature(Project project) {
JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class);
JavaPluginConvention convention = project.getConvention().getPlugin(JavaPluginConvention.class);
extension.registerFeature("mavenOptional",
(feature) -> feature.usingSourceSet(extension.getSourceSets().getByName("main")));
(feature) -> feature.usingSourceSet(convention.getSourceSets().getByName("main")));
AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.getComponents()
.findByName("java");
.findByName("java");
javaComponent.addVariantsFromConfiguration(
project.getConfigurations().findByName("mavenOptionalRuntimeElements"),
ConfigurationVariantDetails::mapToOptional);
@ -131,7 +131,7 @@ class MavenPublishingConventions {
}
private void customizeOrganization(MavenPomOrganization organization) {
organization.getName().set("VMware, Inc.");
organization.getName().set("Pivotal Software, Inc.");
organization.getUrl().set("https://spring.io");
}
@ -144,9 +144,9 @@ class MavenPublishingConventions {
private void customizeDevelopers(MavenPomDeveloperSpec developers) {
developers.developer((developer) -> {
developer.getName().set("Spring");
developer.getEmail().set("ask@spring.io");
developer.getOrganization().set("VMware, Inc.");
developer.getName().set("Pivotal");
developer.getEmail().set("info@pivotal.io");
developer.getOrganization().set("Pivotal Software, Inc.");
developer.getOrganizationUrl().set("https://www.spring.io");
});
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -61,45 +61,36 @@ public class MavenRepositoryPlugin implements Plugin<Project> {
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));
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));
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));
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));
});
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> {

@ -1,5 +1,5 @@
/*
* Copyright 2021-2023 the original author or authors.
* Copyright 2021-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.
@ -44,7 +44,7 @@ public class SyncAppSource extends DefaultTask {
this.sourceDirectory = objects.directoryProperty();
this.destinationDirectory = objects.directoryProperty();
this.pluginVersion = objects.property(String.class)
.convention(getProject().provider(() -> getProject().getVersion().toString()));
.convention(getProject().provider(() -> getProject().getVersion().toString()));
}
@TaskAction

@ -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,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -16,16 +16,14 @@
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.io.Reader;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
@ -40,32 +38,28 @@ import org.gradle.api.tasks.TaskAction;
import org.springframework.asm.ClassReader;
import org.springframework.asm.Opcodes;
import org.springframework.core.CollectionFactory;
import org.springframework.util.StringUtils;
/**
* 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");
.file((Callable<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring.factories"))
.withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("spring.factories");
dependsOn((Callable<String>) () -> this.sourceSet.getProcessResourcesTaskName());
getProject().getConfigurations()
.maybeCreate(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME);
.maybeCreate(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME);
}
public void setSourceSet(SourceSet sourceSet) {
@ -92,7 +86,11 @@ public class AutoConfigurationMetadata extends DefaultTask {
private Properties readAutoConfiguration() throws IOException {
Properties autoConfiguration = CollectionFactory.createSortedProperties(true);
List<String> classNames = readAutoConfigurationsFile();
Properties springFactories = readSpringFactories(
new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories"));
String enableAutoConfiguration = springFactories
.getProperty("org.springframework.boot.autoconfigure.EnableAutoConfiguration");
Set<String> classNames = StringUtils.commaDelimitedListToSet(enableAutoConfiguration);
Set<String> publicClassNames = new LinkedHashSet<>();
for (String className : classNames) {
File classFile = findClassFile(className);
@ -111,30 +109,6 @@ public class AutoConfigurationMetadata extends DefaultTask {
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()) {
@ -146,4 +120,12 @@ public class AutoConfigurationMetadata extends DefaultTask {
return null;
}
private Properties readSpringFactories(File file) throws IOException {
Properties springFactories = new Properties();
try (Reader in = new FileReader(file)) {
springFactories.load(in);
}
return springFactories;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -17,48 +17,29 @@
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.plugins.JavaPluginConvention;
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;
import org.springframework.boot.build.context.properties.ConfigurationPropertiesPlugin;
/**
* {@link Plugin} for projects that define auto-configuration. When applied, the plugin
* applies the {@link DeployedPlugin}. Additionally, when the {@link JavaPlugin} is
* applied it:
* applies the {@link DeployedPlugin}. Additionally, it reacts to the presence of the
* {@link JavaPlugin} by:
*
* <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>
* <li>Applying the {@link ConfigurationPropertiesPlugin}.
* <li>Adding a dependency on the auto-configuration annotation processor.
* <li>Defining a task that produces metadata describing the auto-configuration. The
* metadata is made available as an artifact in the
* </ul>
*
* @author Andy Wilkinson
@ -71,91 +52,29 @@ public class AutoConfigurationPlugin implements Plugin<Project> {
*/
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) -> {
project.getPlugins().apply(ConfigurationPropertiesPlugin.class);
Configuration annotationProcessors = project.getConfigurations()
.getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
.getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
annotationProcessors.getDependencies()
.add(project.getDependencies()
.project(Collections.singletonMap("path",
.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",
.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);
SourceSet main = project.getConvention().getPlugin(JavaPluginConvention.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.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,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -21,13 +21,12 @@ 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 java.util.stream.Collectors;
import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
@ -48,20 +47,24 @@ 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.gradle.util.ConfigureUtil;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.springframework.boot.build.DeployedPlugin;
import org.springframework.boot.build.bom.Library.DependencyConstraintsDependencyVersions;
import org.springframework.boot.build.bom.Library.DependencyLockDependencyVersions;
import org.springframework.boot.build.bom.Library.DependencyVersions;
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.Library.VersionAlignment;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.boot.build.mavenplugin.MavenExec;
import org.springframework.util.FileCopyUtils;
@ -79,7 +82,7 @@ public class BomExtension {
private final List<Library> libraries = new ArrayList<>();
private final UpgradeHandler upgradeHandler;
private final UpgradeHandler upgradeHandler = new UpgradeHandler();
private final DependencyHandler dependencyHandler;
@ -87,7 +90,6 @@ public class BomExtension {
public BomExtension(DependencyHandler dependencyHandler, Project project) {
this.dependencyHandler = dependencyHandler;
this.upgradeHandler = project.getObjects().newInstance(UpgradeHandler.class);
this.project = project;
}
@ -95,8 +97,8 @@ public class BomExtension {
return this.libraries;
}
public void upgrade(Action<UpgradeHandler> action) {
action.execute(this.upgradeHandler);
public void upgrade(Closure<?> closure) {
ConfigureUtil.configure(closure, this.upgradeHandler);
}
public Upgrade getUpgrade() {
@ -104,56 +106,54 @@ public class BomExtension {
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, Closure<?> closure) {
this.library(name, null, closure);
}
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 library(String name, String version, Closure<?> closure) {
LibraryHandler libraryHandler = new LibraryHandler(version);
ConfigureUtil.configure(closure, libraryHandler);
LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version),
libraryHandler.versionAlignment);
addLibrary(new Library(name, libraryVersion, libraryHandler.groups, libraryHandler.prohibitedVersions,
libraryHandler.dependencyVersions));
}
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,
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) {
@ -192,9 +192,8 @@ public class BomExtension {
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()));
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);
@ -214,87 +213,60 @@ public class BomExtension {
private final List<ProhibitedVersion> prohibitedVersions = new ArrayList<>();
private boolean considerSnapshots = false;
private String version;
private String calendarName;
private VersionAlignment versionAlignment;
private DependencyVersions dependencyVersions;
@Inject
public LibraryHandler(String version) {
this.version = version;
}
public void version(String version) {
public void version(String version, Closure<?> closure) {
this.version = version;
VersionHandler versionHandler = new VersionHandler();
ConfigureUtil.configure(closure, versionHandler);
this.versionAlignment = new VersionAlignment(versionHandler.libraryName);
}
public void considerSnapshots() {
this.considerSnapshots = true;
}
public void setCalendarName(String calendarName) {
this.calendarName = calendarName;
}
public void group(String id, Action<GroupHandler> action) {
public void group(String id, Closure<?> closure) {
GroupHandler groupHandler = new GroupHandler(id);
action.execute(groupHandler);
ConfigureUtil.configure(closure, groupHandler);
this.groups
.add(new Group(groupHandler.id, groupHandler.modules, groupHandler.plugins, groupHandler.imports));
.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 void prohibit(String range, Closure<?> closure) {
ProhibitedVersionHandler prohibitedVersionHandler = new ProhibitedVersionHandler();
ConfigureUtil.configure(closure, prohibitedVersionHandler);
try {
this.prohibitedVersions.add(new ProhibitedVersion(VersionRange.createFromVersionSpec(range),
prohibitedVersionHandler.reason));
}
catch (InvalidVersionSpecificationException ex) {
throw new InvalidUserCodeException("Invalid version range", ex);
}
}
public static class 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 dependencyVersions(Closure<?> closure) {
DependencyVersionsHandler dependencyVersionsHandler = new DependencyVersionsHandler();
ConfigureUtil.configure(closure, dependencyVersionsHandler);
}
public void startsWith(String startsWith) {
this.startsWith.add(startsWith);
}
public static class VersionHandler {
public void startsWith(Collection<String> startsWith) {
this.startsWith.addAll(startsWith);
}
private String libraryName;
public void endsWith(String endsWith) {
this.endsWith.add(endsWith);
public void shouldAlignWithVersionFrom(String libraryName) {
this.libraryName = libraryName;
}
public void endsWith(Collection<String> endsWith) {
this.endsWith.addAll(endsWith);
}
}
public void contains(String contains) {
this.contains.add(contains);
}
public static class ProhibitedVersionHandler {
public void contains(List<String> contains) {
this.contains.addAll(contains);
}
private String reason;
public void because(String because) {
this.reason = because;
@ -318,8 +290,8 @@ public class BomExtension {
public void setModules(List<Object> modules) {
this.modules = modules.stream()
.map((input) -> (input instanceof Module module) ? module : new Module((String) input))
.toList();
.map((input) -> (input instanceof Module) ? (Module) input : new Module((String) input))
.collect(Collectors.toList());
}
public void setImports(List<String> imports) {
@ -333,11 +305,9 @@ public class BomExtension {
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) {
if (arg instanceof Closure) {
ModuleHandler moduleHandler = new ModuleHandler();
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
closure.setDelegate(moduleHandler);
closure.call(moduleHandler);
ConfigureUtil.configure((Closure<?>) arg, moduleHandler);
return new Module(name, moduleHandler.type, moduleHandler.classifier, moduleHandler.exclusions);
}
}
@ -368,6 +338,29 @@ public class BomExtension {
}
public class DependencyVersionsHandler {
public void extractFrom(Closure<?> closure) {
ExtractFromHandler extractFromHandler = new ExtractFromHandler();
ConfigureUtil.configure(closure, extractFromHandler);
}
public class ExtractFromHandler {
public void dependencyLock(String location) {
LibraryHandler.this.dependencyVersions = new DependencyLockDependencyVersions(location,
LibraryHandler.this.version);
}
public void dependencyConstraints(String location) {
LibraryHandler.this.dependencyVersions = new DependencyConstraintsDependencyVersions(location,
LibraryHandler.this.version);
}
}
}
}
public static class UpgradeHandler {
@ -380,8 +373,8 @@ public class BomExtension {
this.upgradePolicy = upgradePolicy;
}
public void gitHub(Action<GitHubHandler> action) {
action.execute(this.gitHub);
public void gitHub(Closure<?> closure) {
ConfigureUtil.configure(closure, this.gitHub);
}
}
@ -481,9 +474,8 @@ public class BomExtension {
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));
TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document),
new StreamResult(this.effectiveBom));
}
catch (Exception ex) {
throw new TaskExecutionException(task, ex);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -21,8 +21,8 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import groovy.namespace.QName;
import groovy.util.Node;
import groovy.xml.QName;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
@ -37,7 +37,6 @@ 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;
/**
@ -60,28 +59,25 @@ public class BomPlugin implements Plugin<Project> {
JavaPlatformExtension javaPlatform = project.getExtensions().getByType(JavaPlatformExtension.class);
javaPlatform.allowDependencies();
createApiEnforcedConfiguration(project);
BomExtension bom = project.getExtensions()
.create("bom", BomExtension.class, project.getDependencies(), 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);
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 {
@ -157,19 +153,16 @@ public class BomPlugin implements Plugin<Project> {
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());
});
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());
});
}
}
}
@ -180,15 +173,12 @@ public class BomPlugin implements Plugin<Project> {
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());
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);
@ -209,15 +199,12 @@ public class BomPlugin implements Plugin<Project> {
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());
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) {
@ -268,8 +255,9 @@ public class BomPlugin implements Plugin<Project> {
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())) {
if (child instanceof Node) {
Node node = (Node) child;
if ((node.name() instanceof QName) && name.equals(((QName) node.name()).getLocalPart())) {
return node;
}
if (name.equals(node.name())) {
@ -282,12 +270,15 @@ public class BomPlugin implements Plugin<Project> {
@SuppressWarnings("unchecked")
private List<Node> findChildren(Node parent, String name) {
return parent.children().stream().filter((child) -> isNodeWithName(child, name)).toList();
return (List<Node>) parent.children().stream().filter((child) -> isNodeWithName(child, name))
.collect(Collectors.toList());
}
private boolean isNodeWithName(Object candidate, String name) {
if (candidate instanceof Node node) {
if ((node.name() instanceof QName qname) && name.equals(qname.getLocalPart())) {
if (candidate instanceof Node) {
Node node = (Node) candidate;
if ((node.name() instanceof QName) && name.equals(((QName) node.name()).getLocalPart())) {
return true;
}
if (name.equals(node.name())) {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -59,18 +59,14 @@ public class CheckBom extends DefaultTask {
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());
.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)) {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -16,11 +16,20 @@
package org.springframework.boot.build.bom;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.artifact.versioning.VersionRange;
import org.gradle.api.GradleException;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
@ -34,8 +43,6 @@ public class Library {
private final String name;
private final String calendarName;
private final LibraryVersion version;
private final List<Group> groups;
@ -44,39 +51,32 @@ public class Library {
private final List<ProhibitedVersion> prohibitedVersions;
private final boolean considerSnapshots;
private final DependencyVersions dependencyVersions;
/**
* 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
* @param dependencyVersions the library's dependency versions
*/
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots) {
public Library(String name, LibraryVersion version, List<Group> groups, List<ProhibitedVersion> prohibitedVersions,
DependencyVersions dependencyVersions) {
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;
this.dependencyVersions = dependencyVersions;
}
public String getName() {
return this.name;
}
public String getCalendarName() {
return this.calendarName;
}
public LibraryVersion getVersion() {
return this.version;
}
@ -93,8 +93,8 @@ public class Library {
return this.prohibitedVersions;
}
public boolean isConsiderSnapshots() {
return this.considerSnapshots;
public DependencyVersions getDependencyVersions() {
return this.dependencyVersions;
}
/**
@ -104,20 +104,10 @@ public class Library {
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) {
public ProhibitedVersion(VersionRange range, String reason) {
this.range = range;
this.startsWith = startsWith;
this.endsWith = endsWith;
this.contains = contains;
this.reason = reason;
}
@ -125,18 +115,6 @@ public class Library {
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;
}
@ -147,14 +125,21 @@ public class Library {
private final DependencyVersion version;
public LibraryVersion(DependencyVersion version) {
private final VersionAlignment versionAlignment;
public LibraryVersion(DependencyVersion version, VersionAlignment versionAlignment) {
this.version = version;
this.versionAlignment = versionAlignment;
}
public DependencyVersion getVersion() {
return this.version;
}
public VersionAlignment getVersionAlignment() {
return this.versionAlignment;
}
}
/**
@ -269,4 +254,128 @@ public class Library {
}
public interface DependencyVersions {
String getVersion(String groupId, String artifactId);
default boolean available() {
return true;
}
}
public static class DependencyLockDependencyVersions implements DependencyVersions {
private final Map<String, Map<String, String>> dependencyVersions = new HashMap<>();
private final String sourceTemplate;
private final String libraryVersion;
public DependencyLockDependencyVersions(String sourceTemplate, String libraryVersion) {
this.sourceTemplate = sourceTemplate;
this.libraryVersion = libraryVersion;
}
@Override
public boolean available() {
return !this.libraryVersion.contains("-SNAPSHOT");
}
@Override
public String getVersion(String groupId, String artifactId) {
if (this.dependencyVersions.isEmpty()) {
loadVersions();
}
return this.dependencyVersions.computeIfAbsent(groupId, (key) -> Collections.emptyMap()).get(artifactId);
}
private void loadVersions() {
String source = this.sourceTemplate.replace("<libraryVersion>", this.libraryVersion);
try {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(URI.create(source).toURL().openStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.startsWith("#")) {
String[] components = line.split(":");
Map<String, String> groupDependencies = this.dependencyVersions
.computeIfAbsent(components[0], (key) -> new HashMap<>());
groupDependencies.put(components[1], components[2]);
}
}
}
}
catch (IOException ex) {
throw new GradleException("Failed to load versions from dependency lock file '" + source + "'", ex);
}
}
}
public static class DependencyConstraintsDependencyVersions implements DependencyVersions {
private static final Pattern CONSTRAINT_PATTERN = Pattern.compile("api \"(.+):(.+):(.+)\"");
private final Map<String, Map<String, String>> dependencyVersions = new HashMap<>();
private final String sourceTemplate;
private final String libraryVersion;
public DependencyConstraintsDependencyVersions(String sourceTemplate, String libraryVersion) {
this.sourceTemplate = sourceTemplate;
this.libraryVersion = libraryVersion;
}
@Override
public String getVersion(String groupId, String artifactId) {
if (this.dependencyVersions.isEmpty()) {
loadVersions();
}
return this.dependencyVersions.computeIfAbsent(groupId, (key) -> Collections.emptyMap()).get(artifactId);
}
private void loadVersions() {
String version = this.libraryVersion;
if (version.endsWith("-SNAPSHOT")) {
version = version.substring(0, version.lastIndexOf('.')) + ".x";
}
String source = this.sourceTemplate.replace("<libraryVersion>", version);
try {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(URI.create(source).toURL().openStream()))) {
String line;
while ((line = reader.readLine()) != null) {
Matcher matcher = CONSTRAINT_PATTERN.matcher(line.trim());
if (matcher.matches()) {
Map<String, String> groupDependencies = this.dependencyVersions
.computeIfAbsent(matcher.group(1), (key) -> new HashMap<>());
groupDependencies.put(matcher.group(2), matcher.group(3));
}
}
}
}
catch (IOException ex) {
throw new GradleException(
"Failed to load versions from dependency constraints declared in '" + source + "'", ex);
}
}
}
public static class VersionAlignment {
private final String libraryName;
public VersionAlignment(String libraryName) {
this.libraryName = libraryName;
}
public String getLibraryName() {
return this.libraryName;
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,19 +28,24 @@ import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
public enum UpgradePolicy implements BiPredicate<DependencyVersion, DependencyVersion> {
/**
* Any version.
* All versions more recent than the current version will be suggested as possible
* upgrades.
*/
ANY((candidate, current) -> true),
ANY((candidate, current) -> current.compareTo(candidate) < 0),
/**
* Minor versions of the current major version.
* New minor versions of the current major version will be suggested as possible
* upgrades. For example, if the current version is 1.2.3, all 1.x.y versions after
* 1.2.3 will be suggested. 2.x versions will not be offered.
*/
SAME_MAJOR_VERSION((candidate, current) -> candidate.isSameMajor(current)),
SAME_MAJOR_VERSION(DependencyVersion::isSameMajorAndNewerThan),
/**
* Patch versions of the current minor version.
* New patch versions of the current minor version will be offered as possible
* upgrades. For example, if the current version is 1.2.3, all 1.2.x versions after
* 1.2.3 will be suggested. 1.x versions will not be offered.
*/
SAME_MINOR_VERSION((candidate, current) -> candidate.isSameMinor(current));
SAME_MINOR_VERSION(DependencyVersion::isSameMinorAndNewerThan);
private final BiPredicate<DependencyVersion, DependencyVersion> delegate;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -16,15 +16,32 @@
package org.springframework.boot.build.bom.bomr;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.stream.Collectors;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.DependencyVersions;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.Module;
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
import org.springframework.boot.build.bom.Library.VersionAlignment;
import org.springframework.boot.build.bom.UpgradePolicy;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.StringUtils;
/**
* Interactive {@link UpgradeResolver} that uses command line input to choose the upgrades
@ -34,37 +51,202 @@ import org.springframework.boot.build.bom.Library;
*/
public final class InteractiveUpgradeResolver implements UpgradeResolver {
private final UserInputHandler userInputHandler;
private final VersionResolver versionResolver;
private final UpgradePolicy upgradePolicy;
private final LibraryUpdateResolver libraryUpdateResolver;
private final UserInputHandler userInputHandler;
InteractiveUpgradeResolver(UserInputHandler userInputHandler, LibraryUpdateResolver libraryUpdateResolver) {
InteractiveUpgradeResolver(VersionResolver versionResolver, UpgradePolicy upgradePolicy,
UserInputHandler userInputHandler) {
this.versionResolver = versionResolver;
this.upgradePolicy = upgradePolicy;
this.userInputHandler = userInputHandler;
this.libraryUpdateResolver = libraryUpdateResolver;
}
@Override
public List<Upgrade> resolveUpgrades(Collection<Library> librariesToUpgrade, Collection<Library> libraries) {
public List<Upgrade> resolveUpgrades(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();
return libraries.stream().filter((library) -> !library.getName().equals("Spring Boot"))
.map((library) -> resolveUpgrade(library, librariesByName)).filter(Objects::nonNull)
.collect(Collectors.toList());
}
private Upgrade resolveUpgrade(LibraryWithVersionOptions libraryWithVersionOptions) {
if (libraryWithVersionOptions.getVersionOptions().isEmpty()) {
private Upgrade resolveUpgrade(Library library, Map<String, Library> libraries) {
List<VersionOption> versionOptions = getVersionOptions(library, libraries);
if (versionOptions.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());
VersionOption current = new VersionOption(library.getVersion().getVersion());
VersionOption selected = this.userInputHandler
.selectOption(library.getName() + " " + library.getVersion().getVersion(), versionOptions, current);
return (selected.equals(current)) ? null : new Upgrade(library, selected.version);
}
private List<VersionOption> getVersionOptions(Library library, Map<String, Library> libraries) {
if (library.getVersion().getVersionAlignment() != null) {
return determineAlignedVersionOption(library, libraries);
}
return determineResolvedVersionOptions(library);
}
private List<VersionOption> determineResolvedVersionOptions(Library library) {
Map<String, SortedSet<DependencyVersion>> moduleVersions = new LinkedHashMap<>();
DependencyVersion libraryVersion = library.getVersion().getVersion();
for (Group group : library.getGroups()) {
for (Module module : group.getModules()) {
moduleVersions.put(group.getId() + ":" + module.getName(),
getLaterVersionsForModule(group.getId(), module.getName(), libraryVersion));
}
for (String bom : group.getBoms()) {
moduleVersions.put(group.getId() + ":" + bom,
getLaterVersionsForModule(group.getId(), bom, libraryVersion));
}
for (String plugin : group.getPlugins()) {
moduleVersions.put(group.getId() + ":" + plugin,
getLaterVersionsForModule(group.getId(), plugin, libraryVersion));
}
}
List<DependencyVersion> allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct()
.filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
.collect(Collectors.toList());
if (allVersions.isEmpty()) {
return Collections.emptyList();
}
return allVersions.stream()
.map((version) -> new ResolvedVersionOption(version, getMissingModules(moduleVersions, version)))
.collect(Collectors.toList());
}
private List<VersionOption> determineAlignedVersionOption(Library library, Map<String, Library> libraries) {
VersionOption alignedVersionOption = alignedVersionOption(library, libraries);
if (alignedVersionOption == null) {
return Collections.emptyList();
}
if (!isPermitted(alignedVersionOption.version, library.getProhibitedVersions())) {
throw new InvalidUserDataException("Version alignment failed. Version " + alignedVersionOption.version
+ " from " + library.getName() + " is prohibited");
}
return Collections.singletonList(alignedVersionOption);
}
private VersionOption alignedVersionOption(Library library, Map<String, Library> libraries) {
VersionAlignment versionAlignment = library.getVersion().getVersionAlignment();
Library alignmentLibrary = libraries.get(versionAlignment.getLibraryName());
DependencyVersions dependencyVersions = alignmentLibrary.getDependencyVersions();
if (dependencyVersions == null) {
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ "' as it does not define any dependency versions");
}
if (!dependencyVersions.available()) {
return null;
}
Set<String> versions = new HashSet<>();
for (Group group : library.getGroups()) {
for (Module module : group.getModules()) {
String version = dependencyVersions.getVersion(group.getId(), module.getName());
if (version != null) {
versions.add(version);
}
}
}
if (versions.isEmpty()) {
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ "' as its dependency versions do not include any of this library's modules");
}
if (versions.size() > 1) {
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ "' as it uses multiple different versions of this library's modules");
}
DependencyVersion version = DependencyVersion.parse(versions.iterator().next());
return library.getVersion().getVersion().equals(version) ? null
: new AlignedVersionOption(version, alignmentLibrary);
}
private boolean isPermitted(DependencyVersion dependencyVersion, List<ProhibitedVersion> prohibitedVersions) {
if (prohibitedVersions.isEmpty()) {
return true;
}
for (ProhibitedVersion prohibitedVersion : prohibitedVersions) {
if (prohibitedVersion.getRange()
.containsVersion(new DefaultArtifactVersion(dependencyVersion.toString()))) {
return false;
}
}
return true;
}
private List<String> getMissingModules(Map<String, SortedSet<DependencyVersion>> moduleVersions,
DependencyVersion version) {
List<String> missingModules = new ArrayList<>();
moduleVersions.forEach((name, versions) -> {
if (!versions.contains(version)) {
missingModules.add(name);
}
});
return missingModules;
}
private SortedSet<DependencyVersion> getLaterVersionsForModule(String groupId, String artifactId,
DependencyVersion currentVersion) {
SortedSet<DependencyVersion> versions = this.versionResolver.resolveVersions(groupId, artifactId);
versions.removeIf((candidate) -> !this.upgradePolicy.test(candidate, currentVersion));
return versions;
}
private static class VersionOption {
private final DependencyVersion version;
protected VersionOption(DependencyVersion version) {
this.version = version;
}
@Override
public String toString() {
return this.version.toString();
}
}
private static final class AlignedVersionOption extends VersionOption {
private final Library alignedWith;
private 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() + ")";
}
}
private static final class ResolvedVersionOption extends VersionOption {
private final List<String> missingModules;
private 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,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,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -17,9 +17,8 @@
package org.springframework.boot.build.bom.bomr;
import java.io.StringReader;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
@ -50,48 +49,35 @@ final class MavenMetadataVersionResolver implements VersionResolver {
private final RestTemplate rest;
private final Collection<URI> repositoryUrls;
private final Collection<String> repositoryUrls;
MavenMetadataVersionResolver(Collection<URI> repositoryUrls) {
this(new RestTemplate(Collections.singletonList(new StringHttpMessageConverter())), repositoryUrls);
MavenMetadataVersionResolver(Collection<String> repositoryUrls) {
this(new RestTemplate(Arrays.asList(new StringHttpMessageConverter())), repositoryUrls);
}
MavenMetadataVersionResolver(RestTemplate restTemplate, Collection<URI> repositoryUrls) {
MavenMetadataVersionResolver(RestTemplate restTemplate, Collection<String> 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() + "/");
this.repositoryUrls = repositoryUrls;
}
@Override
public SortedSet<DependencyVersion> resolveVersions(String groupId, String artifactId) {
Set<String> versions = new HashSet<>();
for (URI repositoryUrl : this.repositoryUrls) {
for (String repositoryUrl : this.repositoryUrls) {
versions.addAll(resolveVersions(groupId, artifactId, repositoryUrl));
}
return versions.stream().map(DependencyVersion::parse).collect(Collectors.toCollection(TreeSet::new));
return new TreeSet<>(versions.stream().map(DependencyVersion::parse).collect(Collectors.toSet()));
}
private Set<String> resolveVersions(String groupId, String artifactId, URI repositoryUrl) {
private Set<String> resolveVersions(String groupId, String artifactId, String repositoryUrl) {
Set<String> versions = new HashSet<>();
URI url = repositoryUrl.resolve(groupId.replace('.', '/') + "/" + artifactId + "/maven-metadata.xml");
String url = repositoryUrl + "/" + groupId.replace('.', '/') + "/" + artifactId + "/maven-metadata.xml";
try {
String metadata = this.rest.getForObject(url, String.class);
Document metadataDocument = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(new InputSource(new StringReader(metadata)));
NodeList versionNodes = (NodeList) XPathFactory.newInstance()
.newXPath()
.evaluate("/metadata/versioning/versions/version", metadataDocument, XPathConstants.NODESET);
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());
}

@ -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,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@ 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.
* An upgrade to change a {@link Library} to use a new version}.
*
* @author Andy Wilkinson
*/

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -17,6 +17,7 @@
package org.springframework.boot.build.bom.bomr;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@ -41,14 +42,14 @@ class UpgradeApplicator {
}
Path apply(Upgrade upgrade) throws IOException {
String buildFileContents = Files.readString(this.buildFile);
String buildFileContents = new String(Files.readAllBytes(this.buildFile), StandardCharsets.UTF_8);
Matcher matcher = Pattern.compile("library\\(\"" + upgrade.getLibrary().getName() + "\", \"(.+)\"\\)")
.matcher(buildFileContents);
.matcher(buildFileContents);
if (!matcher.find()) {
matcher = Pattern
.compile("library\\(\"" + upgrade.getLibrary().getName() + "\"\\) \\{\\s+version\\(\"(.+)\"\\)",
Pattern.MULTILINE)
.matcher(buildFileContents);
.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 + "'");
@ -67,7 +68,7 @@ class UpgradeApplicator {
private void updateGradleProperties(Upgrade upgrade, String version) throws IOException {
String property = version.substring(2, version.length() - 1);
String gradlePropertiesContents = Files.readString(this.gradleProperties);
String gradlePropertiesContents = new String(Files.readAllBytes(this.gradleProperties), StandardCharsets.UTF_8);
String modified = gradlePropertiesContents.replace(
property + "=" + upgrade.getLibrary().getVersion().getVersion(), property + "=" + upgrade.getVersion());
overwrite(this.gradleProperties, modified);
@ -81,7 +82,8 @@ class UpgradeApplicator {
}
private void overwrite(Path target, String content) throws IOException {
Files.writeString(target, content, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
Files.write(target, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -16,42 +16,164 @@
package org.springframework.boot.build.bom.bomr;
import java.net.URI;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Task;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskExecutionException;
import org.gradle.api.tasks.options.Option;
import org.springframework.boot.build.bom.BomExtension;
import org.springframework.boot.build.bom.bomr.github.GitHub;
import org.springframework.boot.build.bom.bomr.github.GitHubRepository;
import org.springframework.boot.build.bom.bomr.github.Issue;
import org.springframework.boot.build.bom.bomr.github.Milestone;
import org.springframework.util.StringUtils;
/**
* {@link Task} to upgrade the libraries managed by a bom.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
*/
public abstract class UpgradeBom extends UpgradeDependencies {
public class UpgradeBom extends DefaultTask {
private Set<String> repositoryUrls;
private final BomExtension bom;
private String milestone;
@Inject
public UpgradeBom(BomExtension bom) {
super(bom);
this.bom = bom;
this.repositoryUrls = new LinkedHashSet<>();
getProject().getRepositories().withType(MavenArtifactRepository.class, (repository) -> {
URI repositoryUrl = repository.getUrl();
if (!repositoryUrl.toString().endsWith("snapshot")) {
getRepositoryUris().add(repositoryUrl);
String repositoryUrl = repository.getUrl().toString();
if (!repositoryUrl.endsWith("snapshot")) {
this.repositoryUrls.add(repositoryUrl);
}
});
}
@Override
protected String issueTitle(Upgrade upgrade) {
return "Upgrade to " + upgrade.getLibrary().getName() + " " + upgrade.getVersion();
@Option(option = "milestone", description = "Milestone to which dependency upgrade issues should be assigned")
public void setMilestone(String milestone) {
this.milestone = milestone;
}
@Input
public String getMilestone() {
return this.milestone;
}
@TaskAction
void upgradeDependencies() {
GitHubRepository repository = createGitHub().getRepository(this.bom.getUpgrade().getGitHub().getOrganization(),
this.bom.getUpgrade().getGitHub().getRepository());
List<String> availableLabels = repository.getLabels();
List<String> issueLabels = this.bom.getUpgrade().getGitHub().getIssueLabels();
if (!availableLabels.containsAll(issueLabels)) {
List<String> unknownLabels = new ArrayList<>(issueLabels);
unknownLabels.removeAll(availableLabels);
throw new InvalidUserDataException(
"Unknown label(s): " + StringUtils.collectionToCommaDelimitedString(unknownLabels));
}
Milestone milestone = determineMilestone(repository);
List<Issue> existingUpgradeIssues = repository.findIssues(issueLabels, milestone);
List<Upgrade> upgrades = new InteractiveUpgradeResolver(new MavenMetadataVersionResolver(this.repositoryUrls),
this.bom.getUpgrade().getPolicy(), getServices().get(UserInputHandler.class))
.resolveUpgrades(this.bom.getLibraries());
Path buildFile = getProject().getBuildFile().toPath();
Path gradleProperties = new File(getProject().getRootProject().getProjectDir(), "gradle.properties").toPath();
UpgradeApplicator upgradeApplicator = new UpgradeApplicator(buildFile, gradleProperties);
for (Upgrade upgrade : upgrades) {
String title = "Upgrade to " + upgrade.getLibrary().getName() + " " + upgrade.getVersion();
Issue existingUpgradeIssue = findExistingUpgradeIssue(existingUpgradeIssues, upgrade);
if (existingUpgradeIssue != null) {
System.out.println(title + " (supersedes #" + existingUpgradeIssue.getNumber() + " "
+ existingUpgradeIssue.getTitle() + ")");
}
else {
System.out.println(title);
}
try {
Path modified = upgradeApplicator.apply(upgrade);
int issueNumber = repository.openIssue(title,
(existingUpgradeIssue != null) ? "Supersedes #" + existingUpgradeIssue.getNumber() : "",
issueLabels, milestone);
if (existingUpgradeIssue != null) {
existingUpgradeIssue.label(Arrays.asList("type: task", "status: superseded"));
}
if (new ProcessBuilder().command("git", "add", modified.toFile().getAbsolutePath()).start()
.waitFor() != 0) {
throw new IllegalStateException("git add failed");
}
if (new ProcessBuilder().command("git", "commit", "-m", title + "\n\nCloses gh-" + issueNumber).start()
.waitFor() != 0) {
throw new IllegalStateException("git commit failed");
}
}
catch (IOException ex) {
throw new TaskExecutionException(this, ex);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
private Issue findExistingUpgradeIssue(List<Issue> existingUpgradeIssues, Upgrade upgrade) {
String toMatch = "Upgrade to " + upgrade.getLibrary().getName();
for (Issue existingUpgradeIssue : existingUpgradeIssues) {
if (existingUpgradeIssue.getTitle().substring(0, existingUpgradeIssue.getTitle().lastIndexOf(' '))
.equals(toMatch)) {
return existingUpgradeIssue;
}
}
return null;
}
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);
}
}
@Override
protected String commitMessage(Upgrade upgrade, int issueNumber) {
return issueTitle(upgrade) + "\n\nCloses gh-" + issueNumber;
private Milestone determineMilestone(GitHubRepository repository) {
if (this.milestone == null) {
return null;
}
List<Milestone> milestones = repository.getMilestones();
Optional<Milestone> matchingMilestone = milestones.stream()
.filter((milestone) -> milestone.getName().equals(this.milestone)).findFirst();
if (!matchingMilestone.isPresent()) {
throw new InvalidUserDataException("Unknown milestone: " + this.milestone);
}
return matchingMilestone.get();
}
}

@ -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,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,10 +30,9 @@ interface UpgradeResolver {
/**
* Resolves the upgrades to be applied to the given {@code libraries}.
* @param librariesToUpgrade the libraries to upgrade
* @param libraries all libraries
* @param libraries the libraries
* @return the upgrades
*/
List<Upgrade> resolveUpgrades(Collection<Library> librariesToUpgrade, Collection<Library> libraries);
List<Upgrade> resolveUpgrades(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,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -17,7 +17,6 @@
package org.springframework.boot.build.bom.bomr.github;
import java.util.List;
import java.util.Set;
/**
* Minimal API for interacting with a GitHub repository.
@ -41,7 +40,7 @@ public interface GitHubRepository {
* Returns the labels in the repository.
* @return the labels
*/
Set<String> getLabels();
List<String> getLabels();
/**
* Returns the milestones in the repository.

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -35,13 +35,10 @@ public class Issue {
private final String title;
private final State state;
Issue(RestTemplate rest, int number, String title, State state) {
Issue(RestTemplate rest, int number, String title) {
this.rest = rest;
this.number = number;
this.title = title;
this.state = state;
}
public int getNumber() {
@ -52,10 +49,6 @@ public class Issue {
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
@ -65,30 +58,4 @@ public class Issue {
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,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
package org.springframework.boot.build.bom.bomr.github;
import java.time.OffsetDateTime;
/**
* A milestone in a {@link GitHubRepository GitHub repository}.
*
@ -29,12 +27,9 @@ public class Milestone {
private final int number;
private final OffsetDateTime dueOn;
Milestone(String name, int number, OffsetDateTime dueOn) {
Milestone(String name, int number) {
this.name = name;
this.number = number;
this.dueOn = dueOn;
}
/**
@ -53,10 +48,6 @@ public class Milestone {
return this.number;
}
public OffsetDateTime getDueOn() {
return this.dueOn;
}
@Override
public String toString() {
return this.name + " (" + this.number + ")";

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,17 @@
package org.springframework.boot.build.bom.bomr.github;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
@ -46,14 +51,19 @@ final class StandardGitHub implements GitHub {
@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);
Arrays.asList(new MappingJackson2HttpMessageConverter(new ObjectMapper())));
restTemplate.getInterceptors().add(new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
request.getHeaders().add("User-Agent", StandardGitHub.this.username);
request.getHeaders().add("Authorization", "Basic " + Base64.getEncoder().encodeToString(
(StandardGitHub.this.username + ":" + StandardGitHub.this.password).getBytes()));
request.getHeaders().add("Accept", MediaType.APPLICATION_JSON_VALUE);
return execution.execute(request, body);
}
});
UriTemplateHandler uriTemplateHandler = new DefaultUriBuilderFactory(
"https://api.github.com/repos/" + organization + "/" + name + "/");

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -17,13 +17,11 @@
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 java.util.stream.Collectors;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException.Forbidden;
@ -62,23 +60,22 @@ final class StandardGitHubRepository implements GitHubRepository {
return (Integer) response.getBody().get("number");
}
catch (RestClientException ex) {
if (ex instanceof Forbidden forbidden) {
System.out.println("Received 403 response with headers " + forbidden.getResponseHeaders());
if (ex instanceof Forbidden) {
System.out.println("Received 403 response with headers " + ((Forbidden) ex).getResponseHeaders());
}
throw ex;
}
}
@Override
public Set<String> getLabels() {
return new HashSet<>(get("labels?per_page=100", (label) -> (String) label.get("name")));
public List<String> getLabels() {
return get("labels?per_page=100", (label) -> (String) label.get("name"));
}
@Override
public List<Milestone> getMilestones() {
return get("milestones?per_page=100", (milestone) -> new Milestone((String) milestone.get("title"),
(Integer) milestone.get("number"),
(milestone.get("due_on") != null) ? OffsetDateTime.parse((String) milestone.get("due_on")) : null));
return get("milestones?per_page=100",
(milestone) -> new Milestone((String) milestone.get("title"), (Integer) milestone.get("number")));
}
@Override
@ -86,14 +83,14 @@ final class StandardGitHubRepository implements GitHubRepository {
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"))));
(issue) -> new Issue(this.rest, (Integer) issue.get("number"), (String) issue.get("title")));
}
@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();
List<Map<String, Object>> body = response.getBody();
return body.stream().map(mapper).collect(Collectors.toList());
}
private static void sleep(Duration duration) {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -33,19 +33,11 @@ abstract class AbstractDependencyVersion implements DependencyVersion {
@Override
public int compareTo(DependencyVersion other) {
ComparableVersion otherComparable = (other instanceof AbstractDependencyVersion otherVersion)
? otherVersion.comparableVersion : new ComparableVersion(other.toString());
ComparableVersion otherComparable = (other instanceof AbstractDependencyVersion)
? ((AbstractDependencyVersion) other).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) {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,15 +16,12 @@
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}.
*
@ -35,106 +32,47 @@ class ArtifactVersionDependencyVersion extends AbstractDependencyVersion {
private final ArtifactVersion artifactVersion;
protected ArtifactVersionDependencyVersion(ArtifactVersion artifactVersion) {
super(new ComparableVersion(toNormalizedString(artifactVersion)));
super(new ComparableVersion(artifactVersion.toString()));
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) {
public boolean isNewerThan(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();
return compareTo(other) > 0;
}
@Override
public boolean isSameMinor(DependencyVersion other) {
public boolean isSameMajorAndNewerThan(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);
return extractArtifactVersionDependencyVersion(other).map(this::isSameMajorAndNewerThan).orElse(true);
}
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());
private boolean isSameMajorAndNewerThan(ArtifactVersionDependencyVersion other) {
return this.artifactVersion.getMajorVersion() == other.artifactVersion.getMajorVersion() && isNewerThan(other);
}
@Override
public boolean isSnapshotFor(DependencyVersion candidate) {
if (!isSnapshot() || !(candidate instanceof ArtifactVersionDependencyVersion)) {
public boolean isSameMinorAndNewerThan(DependencyVersion other) {
if (other instanceof ReleaseTrainDependencyVersion) {
return false;
}
return sameMajorMinorIncremental(((ArtifactVersionDependencyVersion) candidate).artifactVersion);
return extractArtifactVersionDependencyVersion(other).map(this::isSameMinorAndNewerThan).orElse(true);
}
@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);
private boolean isSameMinorAndNewerThan(ArtifactVersionDependencyVersion other) {
return this.artifactVersion.getMajorVersion() == other.artifactVersion.getMajorVersion()
&& this.artifactVersion.getMinorVersion() == other.artifactVersion.getMinorVersion()
&& isNewerThan(other);
}
@Override
@ -145,8 +83,8 @@ class ArtifactVersionDependencyVersion extends AbstractDependencyVersion {
protected Optional<ArtifactVersionDependencyVersion> extractArtifactVersionDependencyVersion(
DependencyVersion other) {
ArtifactVersionDependencyVersion artifactVersion = null;
if (other instanceof ArtifactVersionDependencyVersion otherVersion) {
artifactVersion = otherVersion;
if (other instanceof ArtifactVersionDependencyVersion) {
artifactVersion = (ArtifactVersionDependencyVersion) other;
}
return Optional.ofNullable(artifactVersion);
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,6 +41,14 @@ class CalendarVersionDependencyVersion extends ArtifactVersionDependencyVersion
super(artifactVersion, comparableVersion);
}
@Override
public boolean isNewerThan(DependencyVersion other) {
if (other instanceof ReleaseTrainDependencyVersion) {
return true;
}
return super.isNewerThan(other);
}
static CalendarVersionDependencyVersion parse(String version) {
if (!CALENDAR_VERSION_PATTERN.matcher(version).matches()) {
return null;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,43 +28,34 @@ import java.util.function.Function;
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}
* Returns whether this version is newer than the given {@code other} version.
* @param other version to test
* @return {@code true} if this version is newer, otherwise {@code false}
*/
boolean isSameMinor(DependencyVersion other);
boolean isNewerThan(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
* Returns whether this version has the same major versions as the {@code other}
* version while also being newer.
* @param other version to test
* @return {@code true} if this version has the same major and is newer, otherwise
* {@code false}
*/
boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots);
boolean isSameMajorAndNewerThan(DependencyVersion other);
/**
* 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
* Returns whether this version has the same major and minor versions as the
* {@code other} version while also being newer.
* @param other version to test
* @return {@code true} if this version has the same major and minor and is newer,
* otherwise {@code false}
*/
boolean isSnapshotFor(DependencyVersion candidate);
boolean isSameMinorAndNewerThan(DependencyVersion other);
static DependencyVersion parse(String version) {
List<Function<String, DependencyVersion>> parsers = Arrays.asList(CalendarVersionDependencyVersion::parse,
ArtifactVersionDependencyVersion::parse, ReleaseTrainDependencyVersion::parse,
MultipleComponentsDependencyVersion::parse, CombinedPatchAndQualifierDependencyVersion::parse,
NumericQualifierDependencyVersion::parse, CombinedPatchAndQualifierDependencyVersion::parse,
LeadingZeroesDependencyVersion::parse, UnstructuredDependencyVersion::parse);
for (Function<String, DependencyVersion> parser : parsers) {
DependencyVersion result = parser.apply(version);

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

@ -0,0 +1,56 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.bom.bomr.version;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
/**
* A fallback {@link DependencyVersion} to handle versions with four components that
* cannot be handled by {@link ArtifactVersion} because the fourth component is numeric.
*
* @author Andy Wilkinson
*/
final class NumericQualifierDependencyVersion extends ArtifactVersionDependencyVersion {
private final String original;
private NumericQualifierDependencyVersion(ArtifactVersion artifactVersion, String original) {
super(artifactVersion, new ComparableVersion(original));
this.original = original;
}
@Override
public String toString() {
return this.original;
}
static NumericQualifierDependencyVersion parse(String input) {
String[] components = input.split("\\.");
if (components.length == 4) {
ArtifactVersion artifactVersion = new DefaultArtifactVersion(
components[0] + "." + components[1] + "." + components[2]);
if (artifactVersion.getQualifier() != null && artifactVersion.getQualifier().equals(input)) {
return null;
}
return new NumericQualifierDependencyVersion(artifactVersion, input);
}
return null;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -28,8 +28,7 @@ import org.springframework.util.StringUtils;
*/
final class ReleaseTrainDependencyVersion implements DependencyVersion {
private static final Pattern VERSION_PATTERN = Pattern
.compile("([A-Z][a-z]+)-((BUILD-SNAPSHOT)|([A-Z-]+)([0-9]*))");
private static final Pattern VERSION_PATTERN = Pattern.compile("([A-Z][a-z]+)-([A-Z]+)([0-9]*)");
private final String releaseTrain;
@ -48,9 +47,10 @@ final class ReleaseTrainDependencyVersion implements DependencyVersion {
@Override
public int compareTo(DependencyVersion other) {
if (!(other instanceof ReleaseTrainDependencyVersion otherReleaseTrain)) {
if (!(other instanceof ReleaseTrainDependencyVersion)) {
return -1;
}
ReleaseTrainDependencyVersion otherReleaseTrain = (ReleaseTrainDependencyVersion) other;
int comparison = this.releaseTrain.compareTo(otherReleaseTrain.releaseTrain);
if (comparison != 0) {
return comparison;
@ -63,56 +63,32 @@ final class ReleaseTrainDependencyVersion implements DependencyVersion {
}
@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;
public boolean isNewerThan(DependencyVersion other) {
if (other instanceof CalendarVersionDependencyVersion) {
return false;
}
if (movingToSnapshots && !isSnapshot() && candidateReleaseTrain.isSnapshot()) {
if (!(other instanceof ReleaseTrainDependencyVersion)) {
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);
ReleaseTrainDependencyVersion otherReleaseTrain = (ReleaseTrainDependencyVersion) other;
return otherReleaseTrain.compareTo(this) < 0;
}
@Override
public boolean isSameMajor(DependencyVersion other) {
return isSameReleaseTrain(other);
public boolean isSameMajorAndNewerThan(DependencyVersion other) {
return isNewerThan(other);
}
@Override
public boolean isSameMinor(DependencyVersion other) {
return isSameReleaseTrain(other);
}
private boolean isSameReleaseTrain(DependencyVersion other) {
public boolean isSameMinorAndNewerThan(DependencyVersion other) {
if (other instanceof CalendarVersionDependencyVersion) {
return false;
}
if (other instanceof ReleaseTrainDependencyVersion otherReleaseTrain) {
return otherReleaseTrain.releaseTrain.equals(this.releaseTrain);
if (!(other instanceof ReleaseTrainDependencyVersion)) {
return true;
}
return true;
ReleaseTrainDependencyVersion otherReleaseTrain = (ReleaseTrainDependencyVersion) other;
return otherReleaseTrain.releaseTrain.equals(this.releaseTrain) && isNewerThan(other);
}
@Override
@ -148,9 +124,8 @@ final class ReleaseTrainDependencyVersion implements DependencyVersion {
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);
return new ReleaseTrainDependencyVersion(matcher.group(1), matcher.group(2),
(StringUtils.hasLength(matcher.group(3))) ? Integer.parseInt(matcher.group(3)) : 0, input);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,23 +34,23 @@ final class UnstructuredDependencyVersion extends AbstractDependencyVersion impl
}
@Override
public boolean isSameMajor(DependencyVersion other) {
return true;
public boolean isNewerThan(DependencyVersion other) {
return compareTo(other) > 0;
}
@Override
public boolean isSameMinor(DependencyVersion other) {
return true;
public boolean isSameMajorAndNewerThan(DependencyVersion other) {
return compareTo(other) > 0;
}
@Override
public String toString() {
return this.version;
public boolean isSameMinorAndNewerThan(DependencyVersion other) {
return compareTo(other) > 0;
}
@Override
public boolean isSnapshotFor(DependencyVersion candidate) {
return false;
public String toString() {
return this.version;
}
static UnstructuredDependencyVersion parse(String version) {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -34,7 +34,6 @@ 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;
@ -69,10 +68,8 @@ public class CheckClasspathForConflicts extends DefaultTask {
for (File file : this.classpath) {
if (file.isDirectory()) {
Path root = file.toPath();
try (Stream<Path> pathStream = Files.walk(root)) {
pathStream.filter(Files::isRegularFile)
Files.walk(root).filter(Files::isRegularFile)
.forEach((entry) -> classpathContents.add(root.relativize(entry).toString(), root.toString()));
}
}
else {
try (JarFile jar = new JarFile(file)) {
@ -111,11 +108,9 @@ public class CheckClasspathForConflicts extends DefaultTask {
}
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));
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) {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -16,6 +16,7 @@
package org.springframework.boot.build.classpath;
import java.io.IOException;
import java.util.TreeSet;
import java.util.stream.Collectors;
@ -51,14 +52,10 @@ public class CheckClasspathForProhibitedDependencies extends DefaultTask {
}
@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));
public void checkForProhibitedDependencies() throws IOException {
TreeSet<String> prohibited = this.classpath.getResolvedConfiguration().getResolvedArtifacts().stream()
.map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited)
.map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new));
if (!prohibited.isEmpty()) {
StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies:%n"));
for (String dependency : prohibited) {
@ -79,12 +76,6 @@ public class CheckClasspathForProhibitedDependencies extends DefaultTask {
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;
}
@ -100,9 +91,6 @@ public class CheckClasspathForProhibitedDependencies extends DefaultTask {
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,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -18,6 +18,7 @@ package org.springframework.boot.build.classpath;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@ -38,8 +39,6 @@ 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;
@ -63,42 +62,32 @@ public class CheckClasspathForUnnecessaryExclusions extends DefaultTask {
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)));
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);
if (dependency instanceof ModuleDependency) {
processDependency((ModuleDependency) dependency);
}
}
private void processDependency(ModuleDependency dependency) {
String dependencyId = getId(dependency);
TreeSet<String> exclusions = dependency.getExcludeRules()
.stream()
.map(this::getId)
.collect(Collectors.toCollection(TreeSet::new));
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));
@ -116,13 +105,10 @@ public class CheckClasspathForUnnecessaryExclusions extends DefaultTask {
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);
List<String> dependencies = this.configurations.detachedConfiguration(toCheck, this.platform)
.getIncoming().getArtifacts().getArtifacts().stream().map(this::getId)
.collect(Collectors.toList());
exclusions.removeAll(dependencies);
removeProfileExclusions(dependencyId, exclusions);
if (!exclusions.isEmpty()) {
unnecessaryExclusions.put(dependencyId, exclusions);

@ -0,0 +1,112 @@
/*
* 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.cli;
import java.io.File;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.digest.DigestUtils;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFile;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskExecutionException;
import org.springframework.boot.build.artifacts.ArtifactRelease;
/**
* Base class for generating a package manager definition file such as a Scoop manifest or
* a Homebrew formula.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
public abstract class AbstractPackageManagerDefinitionTask extends DefaultTask {
private Provider<RegularFile> archive;
private File template;
private File outputDir;
public AbstractPackageManagerDefinitionTask() {
getInputs().property("version", getProject().provider(getProject()::getVersion));
}
@InputFile
@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);
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,99 +16,17 @@
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);
}
}
public class HomebrewFormula extends AbstractPackageManagerDefinitionTask {
@TaskAction
void createFormula() {

@ -0,0 +1,37 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.cli;
import java.util.Collections;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskAction;
/**
* A {@link Task} for creating a Scoop manifest.
*
* @author Andy Wilkinson
*/
public class ScoopManifest extends AbstractPackageManagerDefinitionTask {
@TaskAction
void createManifest() {
String version = getProject().getVersion().toString();
createDescriptor(Collections.singletonMap("scoopVersion", version.substring(0, version.lastIndexOf('.'))));
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -64,10 +64,8 @@ public class ExtractVersionConstraints extends DefaultTask {
}
public void enforcedPlatform(String projectPath) {
this.configuration.getDependencies()
.add(getProject().getDependencies()
.enforcedPlatform(
getProject().getDependencies().project(Collections.singletonMap("path", projectPath))));
this.configuration.getDependencies().add(getProject().getDependencies().enforcedPlatform(
getProject().getDependencies().project(Collections.singletonMap("path", projectPath))));
this.projectPaths.add(projectPath);
}
@ -91,10 +89,8 @@ public class ExtractVersionConstraints extends DefaultTask {
this.configuration.resolve();
for (String projectPath : this.projectPaths) {
extractVersionProperties(projectPath);
for (DependencyConstraint constraint : getProject().project(projectPath)
.getConfigurations()
.getByName("apiElements")
.getAllDependencyConstraints()) {
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(),

@ -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,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -16,24 +16,21 @@
package org.springframework.boot.build.context.properties;
import java.io.File;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.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.plugins.JavaPluginConvention;
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;
/**
@ -42,13 +39,10 @@ import org.springframework.util.StringUtils;
*
* <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>
@ -63,126 +57,45 @@ public class ConfigurationPropertiesPlugin implements Plugin<Project> {
*/
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);
addConfigurationProcessorDependency(project);
configureAdditionalMetadataLocationsCompilerArgument(project);
registerCheckAdditionalMetadataTask(project);
registerCheckMetadataTask(project);
addMetadataArtifact(project);
});
}
private void configureConfigurationPropertiesAnnotationProcessor(Project project) {
private void addConfigurationProcessorDependency(Project project) {
Configuration annotationProcessors = project.getConfigurations()
.getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
annotationProcessors.getDependencies()
.add(project.getDependencies()
.project(Collections.singletonMap("path",
":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor")));
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));
.getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
annotationProcessors.getDependencies().add(project.getDependencies().project(Collections.singletonMap("path",
":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor")));
}
private void addMetadataArtifact(Project project) {
SourceSet mainSourceSet = project.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
SourceSet mainSourceSet = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
project.getConfigurations().maybeCreate(CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME);
project.afterEvaluate((evaluatedProject) -> evaluatedProject.getArtifacts()
.add(CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME,
mainSourceSet.getJava()
.getDestinationDirectory()
.dir("META-INF/spring-configuration-metadata.json"),
(artifact) -> artifact
project.afterEvaluate((evaluatedProject) -> evaluatedProject.getArtifacts().add(
CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME,
evaluatedProject.provider((Callable<File>) () -> new File(mainSourceSet.getJava().getOutputDir(),
"META-INF/spring-configuration-metadata.json")),
(artifact) -> artifact
.builtBy(evaluatedProject.getTasks().getByName(mainSourceSet.getClassesTaskName()))));
}
private void configureAdditionalMetadataLocationsCompilerArgument(Project project) {
JavaCompile compileJava = project.getTasks()
.withType(JavaCompile.class)
.getByName(JavaPlugin.COMPILE_JAVA_TASK_NAME);
((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()
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.getConvention().getPlugin(JavaPluginConvention.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));
compileJava.getOptions().getCompilerArgs()
.add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + StringUtils
.collectionToCommaDelimitedString(mainSourceSet.getResources().getSourceDirectories().getFiles()
.stream().map(project.getRootProject()::relativePath).collect(Collectors.toSet())));
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -78,7 +78,6 @@ public class DocumentConfigurationProperties extends DefaultTask {
snippets.add("application-properties.security", "Security Properties", this::securityPrefixes);
snippets.add("application-properties.rsocket", "RSocket Properties", this::rsocketPrefixes);
snippets.add("application-properties.actuator", "Actuator Properties", this::actuatorPrefixes);
snippets.add("application-properties.docker-compose", "Docker Compose Properties", this::dockerComposePrefixes);
snippets.add("application-properties.devtools", "Devtools Properties", this::devtoolsPrefixes);
snippets.add("application-properties.testing", "Testing Properties", this::testingPrefixes);
snippets.writeTo(this.outputDir.toPath());
@ -104,9 +103,7 @@ public class DocumentConfigurationProperties extends DefaultTask {
config.accept("spring.profiles");
config.accept("spring.quartz");
config.accept("spring.reactor");
config.accept("spring.ssl");
config.accept("spring.task");
config.accept("spring.threads");
config.accept("spring.mandatory-file-encoding");
config.accept("info");
config.accept("spring.output.ansi.enabled");
@ -128,13 +125,13 @@ public class DocumentConfigurationProperties extends DefaultTask {
private void dataPrefixes(Config config) {
config.accept("spring.couchbase");
config.accept("spring.cassandra");
config.accept("spring.elasticsearch");
config.accept("spring.h2");
config.accept("spring.influx");
config.accept("spring.ldap");
config.accept("spring.mongodb");
config.accept("spring.neo4j");
config.accept("spring.redis");
config.accept("spring.dao");
config.accept("spring.data");
config.accept("spring.datasource");
@ -171,21 +168,19 @@ public class DocumentConfigurationProperties extends DefaultTask {
prefix.accept("spring.integration");
prefix.accept("spring.jms");
prefix.accept("spring.kafka");
prefix.accept("spring.pulsar");
prefix.accept("spring.rabbitmq");
prefix.accept("spring.hazelcast");
prefix.accept("spring.webservices");
}
private void webPrefixes(Config prefix) {
prefix.accept("spring.graphql");
prefix.accept("spring.hateoas");
prefix.accept("spring.http");
prefix.accept("spring.servlet");
prefix.accept("spring.jersey");
prefix.accept("spring.mvc");
prefix.accept("spring.netty");
prefix.accept("spring.resources");
prefix.accept("spring.servlet");
prefix.accept("spring.session");
prefix.accept("spring.web");
prefix.accept("spring.webflux");
@ -215,10 +210,6 @@ public class DocumentConfigurationProperties extends DefaultTask {
prefix.accept("management");
}
private void dockerComposePrefixes(Config prefix) {
prefix.accept("spring.docker.compose");
}
private void devtoolsPrefixes(Config prefix) {
prefix.accept("spring.devtools");
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -45,9 +45,8 @@ class SingleRow extends Row {
return null;
}
if (defaultValue.getClass().isArray()) {
return Arrays.stream((Object[]) defaultValue)
.map(Object::toString)
.collect(Collectors.joining("," + System.lineSeparator()));
return Arrays.stream((Object[]) defaultValue).map(Object::toString)
.collect(Collectors.joining("," + System.lineSeparator()));
}
return defaultValue.toString();
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -52,10 +52,8 @@ class Snippets {
void writeTo(Path outputDirectory) throws IOException {
createDirectory(outputDirectory);
Set<String> remaining = this.properties.stream()
.filter((property) -> !property.isDeprecated())
.map(ConfigurationProperty::getName)
.collect(Collectors.toSet());
Set<String> remaining = this.properties.stream().filter((property) -> !property.isDeprecated())
.map(ConfigurationProperty::getName).collect(Collectors.toSet());
for (Snippet snippet : this.snippets) {
Set<String> written = writeSnippet(outputDirectory, snippet, remaining);
remaining.removeAll(written);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -41,16 +41,15 @@ import org.gradle.api.tasks.TaskAction;
*/
public class DocumentDevtoolsPropertyDefaults extends DefaultTask {
private final Configuration devtools;
private Configuration devtools;
private final RegularFileProperty outputFile;
public DocumentDevtoolsPropertyDefaults() {
this.devtools = getProject().getConfigurations().create("devtools");
this.outputFile = getProject().getObjects().fileProperty();
this.outputFile.convention(getProject().getLayout()
.getBuildDirectory()
.file("docs/generated/using/devtools-property-defaults.adoc"));
this.outputFile.convention(getProject().getLayout().getBuildDirectory()
.file("docs/generated/using/devtools-property-defaults.adoc"));
Map<String, String> dependency = new HashMap<>();
dependency.put("path", ":spring-boot-project:spring-boot-devtools");
dependency.put("configuration", "propertyDefaults");

@ -1,217 +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.docs;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.internal.jvm.Jvm;
/**
* {@link Task} to run an application for the purpose of capturing its output for
* inclusion in the reference documentation.
*
* @author Andy Wilkinson
*/
public class ApplicationRunner extends DefaultTask {
private final RegularFileProperty output = getProject().getObjects().fileProperty();
private final ListProperty<String> args = getProject().getObjects().listProperty(String.class);
private final Property<String> mainClass = getProject().getObjects().property(String.class);
private final Property<String> expectedLogging = getProject().getObjects().property(String.class);
private final Property<String> applicationJar = getProject().getObjects()
.property(String.class)
.convention("/opt/apps/myapp.jar");
private final Map<String, String> normalizations = new HashMap<>();
private FileCollection classpath;
@OutputFile
public RegularFileProperty getOutput() {
return this.output;
}
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
public void setClasspath(FileCollection classpath) {
this.classpath = classpath;
}
@Input
public ListProperty<String> getArgs() {
return this.args;
}
@Input
public Property<String> getMainClass() {
return this.mainClass;
}
@Input
public Property<String> getExpectedLogging() {
return this.expectedLogging;
}
@Input
Map<String, String> getNormalizations() {
return this.normalizations;
}
@Input
public Property<String> getApplicationJar() {
return this.applicationJar;
}
public void normalizeTomcatPort() {
this.normalizations.put("(Tomcat started on port )[\\d]+( \\(http\\))", "$18080$2");
this.normalizations.put("(Tomcat initialized with port )[\\d]+( \\(http\\))", "$18080$2");
}
public void normalizeLiveReloadPort() {
this.normalizations.put("(LiveReload server is running on port )[\\d]+", "$135729");
}
@TaskAction
void runApplication() throws IOException {
List<String> command = new ArrayList<>();
File executable = Jvm.current().getExecutable("java");
command.add(executable.getAbsolutePath());
command.add("-cp");
command.add(this.classpath.getFiles()
.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator)));
command.add(this.mainClass.get());
command.addAll(this.args.get());
File outputFile = this.output.getAsFile().get();
Process process = new ProcessBuilder().redirectOutput(outputFile)
.redirectError(outputFile)
.command(command)
.start();
awaitLogging(process);
process.destroy();
normalizeLogging();
}
private void awaitLogging(Process process) {
long end = System.currentTimeMillis() + 60000;
String expectedLogging = this.expectedLogging.get();
while (System.currentTimeMillis() < end) {
for (String line : outputLines()) {
if (line.contains(expectedLogging)) {
return;
}
}
if (!process.isAlive()) {
throw new IllegalStateException("Process exited before '" + expectedLogging + "' was logged");
}
}
throw new IllegalStateException("'" + expectedLogging + "' was not logged within 60 seconds");
}
private List<String> outputLines() {
Path outputPath = this.output.get().getAsFile().toPath();
try {
return Files.readAllLines(outputPath);
}
catch (IOException ex) {
throw new RuntimeException("Failed to read lines of output from '" + outputPath + "'", ex);
}
}
private void normalizeLogging() {
List<String> outputLines = outputLines();
List<String> normalizedLines = normalize(outputLines);
Path outputPath = this.output.get().getAsFile().toPath();
try {
Files.write(outputPath, normalizedLines);
}
catch (IOException ex) {
throw new RuntimeException("Failed to write normalized lines of output to '" + outputPath + "'", ex);
}
}
private List<String> normalize(List<String> lines) {
List<String> normalizedLines = lines;
Map<String, String> normalizations = new HashMap<>(this.normalizations);
normalizations.put("(Starting .* using Java .* with PID [\\d]+ \\().*( started by ).*( in ).*(\\))",
"$1" + this.applicationJar.get() + "$2myuser$3/opt/apps/$4");
for (Entry<String, String> normalization : normalizations.entrySet()) {
Pattern pattern = Pattern.compile(normalization.getKey());
normalizedLines = normalize(normalizedLines, pattern, normalization.getValue());
}
return normalizedLines;
}
private List<String> normalize(List<String> lines, Pattern pattern, String replacement) {
boolean matched = false;
List<String> normalizedLines = new ArrayList<>();
for (String line : lines) {
Matcher matcher = pattern.matcher(line);
StringBuilder transformed = new StringBuilder();
while (matcher.find()) {
matched = true;
matcher.appendReplacement(transformed, replacement);
}
matcher.appendTail(transformed);
normalizedLines.add(transformed.toString());
}
if (!matched) {
reportUnmatchedNormalization(lines, pattern);
}
return normalizedLines;
}
private void reportUnmatchedNormalization(List<String> lines, Pattern pattern) {
StringBuilder message = new StringBuilder(
"'" + pattern + "' did not match any of the following lines of output:");
message.append(String.format("%n"));
for (String line : lines) {
message.append(String.format("%s%n", line));
}
throw new IllegalStateException(message.toString());
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -21,11 +21,10 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.gradle.api.DefaultTask;
import org.gradle.api.Task;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
@ -47,8 +46,6 @@ public class DocumentPluginGoals extends DefaultTask {
private File outputDir;
private Map<String, String> goalSections;
@OutputDirectory
public File getOutputDir() {
return this.outputDir;
@ -58,15 +55,6 @@ public class DocumentPluginGoals extends DefaultTask {
this.outputDir = outputDir;
}
@Input
public Map<String, String> getGoalSections() {
return this.goalSections;
}
public void setGoalSections(Map<String, String> goalSections) {
this.goalSections = goalSections;
}
@InputFile
public File getPluginXml() {
return this.pluginXml;
@ -92,7 +80,7 @@ public class DocumentPluginGoals extends DefaultTask {
writer.println("| Goal | Description");
writer.println();
for (Mojo mojo : plugin.getMojos()) {
writer.printf("| <<%s,%s:%s>>%n", goalSectionId(mojo), plugin.getGoalPrefix(), mojo.getGoal());
writer.printf("| <<goals-%s,%s:%s>>%n", mojo.getGoal(), plugin.getGoalPrefix(), mojo.getGoal());
writer.printf("| %s%n", mojo.getDescription());
writer.println();
}
@ -102,7 +90,7 @@ public class DocumentPluginGoals extends DefaultTask {
private void documentMojo(Plugin plugin, Mojo mojo) throws IOException {
try (PrintWriter writer = new PrintWriter(new FileWriter(new File(this.outputDir, mojo.getGoal() + ".adoc")))) {
String sectionId = goalSectionId(mojo);
String sectionId = "goals-" + mojo.getGoal();
writer.println();
writer.println();
writer.printf("[[%s]]%n", sectionId);
@ -110,23 +98,25 @@ public class DocumentPluginGoals extends DefaultTask {
writer.printf("`%s:%s:%s`%n", plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion());
writer.println();
writer.println(mojo.getDescription());
List<Parameter> parameters = mojo.getParameters().stream().filter(Parameter::isEditable).toList();
List<Parameter> requiredParameters = parameters.stream().filter(Parameter::isRequired).toList();
String detailsSectionId = sectionId + ".parameter-details";
List<Parameter> parameters = mojo.getParameters().stream().filter(Parameter::isEditable)
.collect(Collectors.toList());
List<Parameter> requiredParameters = parameters.stream().filter(Parameter::isRequired)
.collect(Collectors.toList());
String parametersSectionId = sectionId + "-parameters";
String detailsSectionId = parametersSectionId + "-details";
if (!requiredParameters.isEmpty()) {
writer.println();
writer.println();
writer.printf("[[%s.required-parameters]]%n", sectionId);
writer.printf("[[%s-required]]%n", parametersSectionId);
writer.println("== Required parameters");
writeParametersTable(writer, detailsSectionId, requiredParameters);
}
List<Parameter> optionalParameters = parameters.stream()
.filter((parameter) -> !parameter.isRequired())
.toList();
List<Parameter> optionalParameters = parameters.stream().filter((parameter) -> !parameter.isRequired())
.collect(Collectors.toList());
if (!optionalParameters.isEmpty()) {
writer.println();
writer.println();
writer.printf("[[%s.optional-parameters]]%n", sectionId);
writer.printf("[[%s-optional]]%n", parametersSectionId);
writer.println("== Optional parameters");
writeParametersTable(writer, detailsSectionId, optionalParameters);
}
@ -138,15 +128,6 @@ public class DocumentPluginGoals extends DefaultTask {
}
}
private String goalSectionId(Mojo mojo) {
String goalSection = this.goalSections.get(mojo.getGoal());
if (goalSection == null) {
throw new IllegalStateException("Goal '" + mojo.getGoal() + "' has not be assigned to a section");
}
String sectionId = goalSection + "." + mojo.getGoal() + "-goal";
return sectionId;
}
private void writeParametersTable(PrintWriter writer, String detailsSectionId, List<Parameter> parameters) {
writer.println("[cols=\"3,2,3\"]");
writer.println("|===");
@ -154,7 +135,7 @@ public class DocumentPluginGoals extends DefaultTask {
writer.println();
for (Parameter parameter : parameters) {
String name = parameter.getName();
writer.printf("| <<%s.%s,%s>>%n", detailsSectionId, parameterId(name), name);
writer.printf("| <<%s-%s,%s>>%n", detailsSectionId, name, name);
writer.printf("| `%s`%n", typeNameToJavadocLink(shortTypeName(parameter.getType()), parameter.getType()));
String defaultValue = parameter.getDefaultValue();
if (defaultValue != null) {
@ -173,7 +154,7 @@ public class DocumentPluginGoals extends DefaultTask {
String name = parameter.getName();
writer.println();
writer.println();
writer.printf("[[%s.%s]]%n", sectionId, parameterId(name));
writer.printf("[[%s-%s]]%n", sectionId, name);
writer.printf("=== `%s`%n", name);
writer.println(parameter.getDescription());
writer.println();
@ -189,20 +170,6 @@ public class DocumentPluginGoals extends DefaultTask {
}
}
private String parameterId(String name) {
StringBuilder id = new StringBuilder(name.length() + 4);
for (char c : name.toCharArray()) {
if (Character.isLowerCase(c)) {
id.append(c);
}
else {
id.append("-");
id.append(Character.toLowerCase(c));
}
}
return id.toString();
}
private void writeDetail(PrintWriter writer, String name, String value) {
writer.printf("| %s%n", name);
writer.printf("| `%s`%n", value);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -43,17 +43,16 @@ public class MavenExec extends JavaExec {
private File projectDir;
public MavenExec() {
public MavenExec() throws IOException {
setClasspath(mavenConfiguration(getProject()));
args("--batch-mode");
getMainClass().set("org.apache.maven.cli.MavenCli");
setMain("org.apache.maven.cli.MavenCli");
}
public void setProjectDir(File projectDir) {
this.projectDir = projectDir;
getInputs().file(new File(projectDir, "pom.xml"))
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("pom");
getInputs().file(new File(projectDir, "pom.xml")).withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("pom");
}
@Override
@ -86,14 +85,13 @@ public class MavenExec extends JavaExec {
return existing;
}
return project.getConfigurations().create("maven", (maven) -> {
maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-embedder:3.6.3"));
maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-compat:3.6.3"));
maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-embedder:3.6.2"));
maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-compat:3.6.2"));
maven.getDependencies().add(project.getDependencies().create("org.slf4j:slf4j-simple:1.7.5"));
maven.getDependencies()
.add(project.getDependencies()
.create("org.apache.maven.resolver:maven-resolver-connector-basic:1.4.1"));
maven.getDependencies()
.add(project.getDependencies().create("org.apache.maven.resolver:maven-resolver-transport-http:1.4.1"));
maven.getDependencies().add(
project.getDependencies().create("org.apache.maven.resolver:maven-resolver-connector-basic:1.4.1"));
maven.getDependencies().add(
project.getDependencies().create("org.apache.maven.resolver:maven-resolver-transport-http:1.4.1"));
});
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -38,6 +38,9 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import io.spring.javaformat.config.IndentationStyle;
import io.spring.javaformat.config.JavaBaseline;
import io.spring.javaformat.config.JavaFormatConfig;
import io.spring.javaformat.formatter.FileEdit;
import io.spring.javaformat.formatter.FileFormatter;
import org.gradle.api.DefaultTask;
@ -61,7 +64,7 @@ import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
@ -77,7 +80,6 @@ import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.Sync;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskExecutionException;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.javadoc.Javadoc;
import org.gradle.external.javadoc.StandardJavadocDocletOptions;
@ -99,6 +101,20 @@ import org.springframework.util.Assert;
*/
public class MavenPluginPlugin implements Plugin<Project> {
private static final JavaFormatConfig FORMATTER_CONFIG = new JavaFormatConfig() {
@Override
public JavaBaseline getJavaBaseline() {
return JavaBaseline.V8;
}
@Override
public IndentationStyle getIndentationStyle() {
return IndentationStyle.TABS;
}
};
@Override
public void apply(Project project) {
project.getPlugins().apply(JavaLibraryPlugin.class);
@ -129,15 +145,15 @@ public class MavenPluginPlugin implements Plugin<Project> {
private void addPopulateIntTestMavenRepositoryTask(Project project) {
Configuration runtimeClasspathWithMetadata = project.getConfigurations().create("runtimeClasspathWithMetadata");
runtimeClasspathWithMetadata
.extendsFrom(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
.extendsFrom(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
runtimeClasspathWithMetadata.attributes((attributes) -> attributes.attribute(DocsType.DOCS_TYPE_ATTRIBUTE,
project.getObjects().named(DocsType.class, "maven-repository")));
RuntimeClasspathMavenRepository runtimeClasspathMavenRepository = project.getTasks()
.create("runtimeClasspathMavenRepository", RuntimeClasspathMavenRepository.class);
.create("runtimeClasspathMavenRepository", RuntimeClasspathMavenRepository.class);
runtimeClasspathMavenRepository.getOutputDirectory()
.set(new File(project.getBuildDir(), "runtime-classpath-repository"));
.set(new File(project.getBuildDir(), "runtime-classpath-repository"));
project.getDependencies()
.components((components) -> components.all(MavenRepositoryComponentMetadataRule.class));
.components((components) -> components.all(MavenRepositoryComponentMetadataRule.class));
Sync task = project.getTasks().create("populateIntTestMavenRepository", Sync.class);
task.setDestinationDir(new File(project.getBuildDir(), "int-test-maven-repository"));
task.with(copyIntTestMavenRepositoryFiles(project, runtimeClasspathMavenRepository));
@ -173,7 +189,7 @@ public class MavenPluginPlugin implements Plugin<Project> {
private MavenExec createGenerateHelpMojoTask(Project project, File helpMojoDir) {
MavenExec task = project.getTasks().create("generateHelpMojo", MavenExec.class);
task.setProjectDir(helpMojoDir);
task.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.1:helpmojo");
task.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.0:helpmojo");
task.getOutputs().dir(new File(helpMojoDir, "target/generated-sources/plugin"));
return task;
}
@ -209,7 +225,7 @@ public class MavenPluginPlugin implements Plugin<Project> {
}
private SourceSet getMainSourceSet(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
return sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
}
@ -220,8 +236,8 @@ public class MavenPluginPlugin implements Plugin<Project> {
private FormatHelpMojoSource createFormatHelpMojoSource(Project project, MavenExec generateHelpMojoTask,
File generatedHelpMojoDir) {
FormatHelpMojoSource formatHelpMojoSource = project.getTasks()
.create("formatHelpMojoSource", FormatHelpMojoSource.class);
FormatHelpMojoSource formatHelpMojoSource = project.getTasks().create("formatHelpMojoSource",
FormatHelpMojoSource.class);
formatHelpMojoSource.setGenerator(generateHelpMojoTask);
formatHelpMojoSource.setOutputDir(generatedHelpMojoDir);
return formatHelpMojoSource;
@ -240,12 +256,10 @@ public class MavenPluginPlugin implements Plugin<Project> {
private MavenExec createGeneratePluginDescriptorTask(Project project, File mavenDir) {
MavenExec generatePluginDescriptor = project.getTasks().create("generatePluginDescriptor", MavenExec.class);
generatePluginDescriptor.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.1:descriptor");
generatePluginDescriptor.args("org.apache.maven.plugins:maven-plugin-plugin:3.6.0:descriptor");
generatePluginDescriptor.getOutputs().dir(new File(mavenDir, "target/classes/META-INF/maven"));
generatePluginDescriptor.getInputs()
.dir(new File(mavenDir, "target/classes/org"))
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("plugin classes");
generatePluginDescriptor.getInputs().dir(new File(mavenDir, "target/classes/org"))
.withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("plugin classes");
generatePluginDescriptor.setProjectDir(mavenDir);
return generatePluginDescriptor;
}
@ -256,15 +270,9 @@ public class MavenPluginPlugin implements Plugin<Project> {
}
private void addPrepareMavenBinariesTask(Project project) {
TaskProvider<PrepareMavenBinaries> task = project.getTasks()
.register("prepareMavenBinaries", PrepareMavenBinaries.class, (prepareMavenBinaries) -> prepareMavenBinaries
.setOutputDir(new File(project.getBuildDir(), "maven-binaries")));
project.getTasks()
.getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME)
.getInputs()
.dir(task.map(PrepareMavenBinaries::getOutputDir))
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("mavenBinaries");
PrepareMavenBinaries task = project.getTasks().create("prepareMavenBinaries", PrepareMavenBinaries.class);
task.setOutputDir(new File(project.getBuildDir(), "maven-binaries"));
project.getTasks().getByName(IntegrationTestPlugin.INT_TEST_TASK_NAME).dependsOn(task);
}
private void replaceVersionPlaceholder(CopySpec copy, Project project) {
@ -276,13 +284,10 @@ public class MavenPluginPlugin implements Plugin<Project> {
}
private void addExtractVersionPropertiesTask(Project project) {
ExtractVersionProperties extractVersionProperties = project.getTasks()
.create("extractVersionProperties", ExtractVersionProperties.class);
ExtractVersionProperties extractVersionProperties = project.getTasks().create("extractVersionProperties",
ExtractVersionProperties.class);
extractVersionProperties.setEffectiveBoms(project.getConfigurations().create("versionProperties"));
extractVersionProperties.getDestination()
.set(project.getLayout()
.getBuildDirectory()
.dir("generated-resources")
extractVersionProperties.getDestination().set(project.getLayout().getBuildDirectory().dir("generated-resources")
.map((dir) -> dir.file("extracted-versions.properties")));
}
@ -294,9 +299,8 @@ public class MavenPluginPlugin implements Plugin<Project> {
void setGenerator(Task generator) {
this.generator = generator;
getInputs().files(this.generator)
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("generated source");
getInputs().files(this.generator).withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("generated source");
}
@OutputDirectory
@ -310,10 +314,10 @@ public class MavenPluginPlugin implements Plugin<Project> {
@TaskAction
void syncAndFormat() {
FileFormatter formatter = new FileFormatter();
FileFormatter formatter = new FileFormatter(FORMATTER_CONFIG);
for (File output : this.generator.getOutputs().getFiles()) {
formatter.formatFiles(getProject().fileTree(output), StandardCharsets.UTF_8)
.forEach((edit) -> save(output, edit));
.forEach((edit) -> save(output, edit));
}
}
@ -322,7 +326,7 @@ public class MavenPluginPlugin implements Plugin<Project> {
Path outputLocation = this.outputDir.toPath().resolve(relativePath);
try {
Files.createDirectories(outputLocation.getParent());
Files.writeString(outputLocation, edit.getFormattedContent());
Files.write(outputLocation, edit.getFormattedContent().getBytes(StandardCharsets.UTF_8));
}
catch (Exception ex) {
throw new TaskExecutionException(this, ex);
@ -342,11 +346,10 @@ public class MavenPluginPlugin implements Plugin<Project> {
@Override
public void execute(ComponentMetadataContext context) {
context.getDetails()
.maybeAddVariant("compileWithMetadata", "compile", (variant) -> configureVariant(context, variant));
context.getDetails()
.maybeAddVariant("apiElementsWithMetadata", "apiElements",
(variant) -> configureVariant(context, variant));
context.getDetails().maybeAddVariant("compileWithMetadata", "compile",
(variant) -> configureVariant(context, variant));
context.getDetails().maybeAddVariant("apiElementsWithMetadata", "apiElements",
(variant) -> configureVariant(context, variant));
}
private void configureVariant(ComponentMetadataContext context, VariantMetadata variant) {
@ -387,15 +390,14 @@ public class MavenPluginPlugin implements Plugin<Project> {
@TaskAction
public void createRepository() {
for (ResolvedArtifactResult result : this.runtimeClasspath.getIncoming().getArtifacts()) {
if (result.getId().getComponentIdentifier() instanceof ModuleComponentIdentifier identifier) {
String fileName = result.getFile()
.getName()
.replace(identifier.getVersion() + "-" + identifier.getVersion(), identifier.getVersion());
File repositoryLocation = this.outputDirectory
.dir(identifier.getGroup().replace('.', '/') + "/" + identifier.getModule() + "/"
+ identifier.getVersion() + "/" + fileName)
.get()
.getAsFile();
if (result.getId().getComponentIdentifier() instanceof ModuleComponentIdentifier) {
ModuleComponentIdentifier identifier = (ModuleComponentIdentifier) result.getId()
.getComponentIdentifier();
String fileName = result.getFile().getName()
.replace(identifier.getVersion() + "-" + identifier.getVersion(), identifier.getVersion());
File repositoryLocation = this.outputDirectory.dir(identifier.getGroup().replace('.', '/') + "/"
+ identifier.getModule() + "/" + identifier.getVersion() + "/" + fileName).get()
.getAsFile();
repositoryLocation.getParentFile().mkdirs();
try {
Files.copy(result.getFile().toPath(), repositoryLocation.toPath(),
@ -463,8 +465,6 @@ public class MavenPluginPlugin implements Plugin<Project> {
effectiveBom.property("spring-framework.version", versions::setProperty);
effectiveBom.property("jakarta-servlet.version", versions::setProperty);
effectiveBom.property("kotlin.version", versions::setProperty);
effectiveBom.property("assertj.version", versions::setProperty);
effectiveBom.property("junit-jupiter.version", versions::setProperty);
return versions;
}
@ -504,8 +504,8 @@ public class MavenPluginPlugin implements Plugin<Project> {
private String get(String expression) {
try {
Node node = (Node) this.xpath.compile("/project/" + expression)
.evaluate(this.document, XPathConstants.NODE);
Node node = (Node) this.xpath.compile("/project/" + expression).evaluate(this.document,
XPathConstants.NODE);
String text = (node != null) ? node.getTextContent() : null;
Assert.hasLength(text, () -> "No result for expression " + expression);
return text;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -99,10 +99,9 @@ class PluginXmlParser {
private Parameter parseParameter(Node parameterNode, Map<String, String> defaultValues,
Map<String, String> userProperties) throws XPathExpressionException {
String description = textAt("description", parameterNode);
return new Parameter(textAt("name", parameterNode), textAt("type", parameterNode),
booleanAt("required", parameterNode), booleanAt("editable", parameterNode),
(description != null) ? format(description) : "", defaultValues.get(textAt("name", parameterNode)),
format(textAt("description", parameterNode)), defaultValues.get(textAt("name", parameterNode)),
userProperties.get(textAt("name", parameterNode)), textAt("since", parameterNode));
}
@ -111,17 +110,10 @@ class PluginXmlParser {
}
private String format(String input) {
return input.replace("<code>", "`")
.replace("</code>", "`")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("<br>", " ")
.replace("\n", " ")
.replace("&quot;", "\"")
.replaceAll("\\{@code (.*?)}", "`$1`")
.replaceAll("\\{@link (.*?)}", "`$1`")
.replaceAll("\\{@literal (.*?)}", "`$1`")
.replaceAll("<a href=.\"(.*?)\".>(.*?)</a>", "$1[$2]");
return input.replace("<code>", "`").replace("</code>", "`").replace("&lt;", "<").replace("&gt;", ">")
.replace("<br>", " ").replace("\n", " ").replace("&quot;", "\"").replaceAll("\\{@code (.*?)}", "`$1`")
.replaceAll("\\{@link (.*?)}", "`$1`").replaceAll("\\{@literal (.*?)}", "`$1`")
.replaceAll("<a href=.\"(.*?)\".>(.*?)</a>", "\\$1[\\$2]");
}
private static final class IterableNodeList implements Iterable<Node> {
@ -139,7 +131,7 @@ class PluginXmlParser {
@Override
public Iterator<Node> iterator() {
return new Iterator<>() {
return new Iterator<Node>() {
private int index = 0;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -35,7 +35,7 @@ import org.gradle.api.tasks.TaskAction;
*/
public class PrepareMavenBinaries extends DefaultTask {
private final Set<String> versions = new LinkedHashSet<>();
private Set<String> versions = new LinkedHashSet<>();
private File outputDir;
@ -60,11 +60,10 @@ public class PrepareMavenBinaries extends DefaultTask {
@TaskAction
public void prepareBinaries() {
for (String version : this.versions) {
Configuration configuration = getProject().getConfigurations()
.detachedConfiguration(
getProject().getDependencies().create("org.apache.maven:apache-maven:" + version + ":bin@zip"));
getProject()
.copy((copy) -> copy.into(this.outputDir).from(getProject().zipTree(configuration.getSingleFile())));
Configuration configuration = getProject().getConfigurations().detachedConfiguration(
getProject().getDependencies().create("org.apache.maven:apache-maven:" + version + ":bin@zip"));
getProject().copy(
(copy) -> copy.into(this.outputDir).from(getProject().zipTree(configuration.getSingleFile())));
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -20,7 +20,7 @@ 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.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSetContainer;
/**
@ -44,16 +44,13 @@ public class OptionalDependenciesPlugin implements Plugin<Project> {
optional.setCanBeConsumed(false);
optional.setCanBeResolved(false);
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
SourceSetContainer sourceSets = project.getExtensions()
.getByType(JavaPluginExtension.class)
.getSourceSets();
SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class)
.getSourceSets();
sourceSets.all((sourceSet) -> {
project.getConfigurations()
.getByName(sourceSet.getCompileClasspathConfigurationName())
.extendsFrom(optional);
project.getConfigurations()
.getByName(sourceSet.getRuntimeClasspathConfigurationName())
.extendsFrom(optional);
project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName())
.extendsFrom(optional);
project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName())
.extendsFrom(optional);
});
});
}

@ -1,39 +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.processors;
import com.diffplug.gradle.eclipse.apt.AptEclipsePlugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
/**
* A plugin for a project that uses one or more annotations processors.
*
* @author Andy Wilkinson
*/
public class ProcessedAnnotationsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(AptEclipsePlugin.class);
project.getExtensions()
.getByType(EclipseModel.class)
.synchronizationTasks("eclipseJdtApt", "eclipseJdt", "eclipseFactorypath");
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -84,10 +84,8 @@ public class DocumentStarters extends DefaultTask {
@TaskAction
void documentStarters() {
Set<Starter> starters = this.starters.getFiles()
.stream()
.map(this::loadStarter)
.collect(Collectors.toCollection(TreeSet::new));
Set<Starter> starters = this.starters.getFiles().stream().map(this::loadStarter)
.collect(Collectors.toCollection(TreeSet::new));
writeTable("application-starters", starters.stream().filter(Starter::isApplication));
writeTable("production-starters", starters.stream().filter(Starter::isProduction));
writeTable("technical-starters", starters.stream().filter(Starter::isTechnical));

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -73,13 +73,8 @@ public class StarterMetadata extends DefaultTask {
Properties properties = CollectionFactory.createSortedProperties(true);
properties.setProperty("name", getProject().getName());
properties.setProperty("description", getProject().getDescription());
properties.setProperty("dependencies",
String.join(",",
this.dependencies.getResolvedConfiguration()
.getResolvedArtifacts()
.stream()
.map(ResolvedArtifact::getName)
.collect(Collectors.toSet())));
properties.setProperty("dependencies", String.join(",", this.dependencies.getResolvedConfiguration()
.getResolvedArtifacts().stream().map(ResolvedArtifact::getName).collect(Collectors.toSet())));
this.destination.getParentFile().mkdirs();
try (FileWriter writer = new FileWriter(this.destination)) {
properties.store(writer, null);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* 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.
@ -33,7 +33,6 @@ import org.gradle.api.tasks.bundling.Jar;
import org.springframework.boot.build.ConventionsPlugin;
import org.springframework.boot.build.DeployedPlugin;
import org.springframework.boot.build.classpath.CheckClasspathForConflicts;
import org.springframework.boot.build.classpath.CheckClasspathForUnconstrainedDirectDependencies;
import org.springframework.boot.build.classpath.CheckClasspathForUnnecessaryExclusions;
import org.springframework.util.StringUtils;
@ -59,42 +58,29 @@ public class StarterPlugin implements Plugin<Project> {
File destination = new File(project.getBuildDir(), "starter-metadata.properties");
starterMetadata.setDestination(destination);
configurations.create("starterMetadata");
project.getArtifacts()
.add("starterMetadata", project.provider(starterMetadata::getDestination),
(artifact) -> artifact.builtBy(starterMetadata));
project.getArtifacts().add("starterMetadata", project.provider(starterMetadata::getDestination),
(artifact) -> artifact.builtBy(starterMetadata));
createClasspathConflictsCheck(runtimeClasspath, project);
createUnnecessaryExclusionsCheck(runtimeClasspath, project);
createUnconstrainedDirectDependenciesCheck(runtimeClasspath, project);
configureJarManifest(project);
}
private void createClasspathConflictsCheck(Configuration classpath, Project project) {
CheckClasspathForConflicts checkClasspathForConflicts = project.getTasks()
.create("check" + StringUtils.capitalize(classpath.getName() + "ForConflicts"),
CheckClasspathForConflicts.class);
CheckClasspathForConflicts checkClasspathForConflicts = project.getTasks().create(
"check" + StringUtils.capitalize(classpath.getName() + "ForConflicts"),
CheckClasspathForConflicts.class);
checkClasspathForConflicts.setClasspath(classpath);
project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForConflicts);
}
private void createUnnecessaryExclusionsCheck(Configuration classpath, Project project) {
CheckClasspathForUnnecessaryExclusions checkClasspathForUnnecessaryExclusions = project.getTasks()
.create("check" + StringUtils.capitalize(classpath.getName() + "ForUnnecessaryExclusions"),
CheckClasspathForUnnecessaryExclusions.class);
CheckClasspathForUnnecessaryExclusions checkClasspathForUnnecessaryExclusions = project.getTasks().create(
"check" + StringUtils.capitalize(classpath.getName() + "ForUnnecessaryExclusions"),
CheckClasspathForUnnecessaryExclusions.class);
checkClasspathForUnnecessaryExclusions.setClasspath(classpath);
project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForUnnecessaryExclusions);
}
private void createUnconstrainedDirectDependenciesCheck(Configuration classpath, Project project) {
CheckClasspathForUnconstrainedDirectDependencies checkClasspathForUnconstrainedDirectDependencies = project
.getTasks()
.create("check" + StringUtils.capitalize(classpath.getName() + "ForUnconstrainedDirectDependencies"),
CheckClasspathForUnconstrainedDirectDependencies.class);
checkClasspathForUnconstrainedDirectDependencies.setClasspath(classpath);
project.getTasks()
.getByName(JavaBasePlugin.CHECK_TASK_NAME)
.dependsOn(checkClasspathForUnconstrainedDirectDependencies);
}
private void configureJarManifest(Project project) {
project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> {
jar.manifest((manifest) -> {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,7 +19,7 @@ package org.springframework.boot.build.test;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.testing.Test;
@ -55,13 +55,13 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(intTest);
project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> {
EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class);
eclipse.classpath((classpath) -> classpath.getPlusConfigurations()
.add(project.getConfigurations().getByName(intTestSourceSet.getRuntimeClasspathConfigurationName())));
eclipse.classpath((classpath) -> classpath.getPlusConfigurations().add(
project.getConfigurations().getByName(intTestSourceSet.getRuntimeClasspathConfigurationName())));
});
}
private SourceSet createSourceSet(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
SourceSet intTestSourceSet = sourceSets.create(INT_TEST_SOURCE_SET_NAME);
SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
intTestSourceSet.setCompileClasspath(intTestSourceSet.getCompileClasspath().plus(main.getOutput()));

@ -1,96 +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.test;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.testing.Test;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
/**
* A {@link Plugin} to configure system testing support in a {@link Project}.
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
public class SystemTestPlugin implements Plugin<Project> {
private static final Spec<Task> NEVER = (task) -> false;
/**
* Name of the {@code systemTest} task.
*/
public static String SYSTEM_TEST_TASK_NAME = "systemTest";
/**
* Name of the {@code systemTest} source set.
*/
public static String SYSTEM_TEST_SOURCE_SET_NAME = "systemTest";
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureSystemTesting(project));
}
private void configureSystemTesting(Project project) {
SourceSet systemTestSourceSet = createSourceSet(project);
createTestTask(project, systemTestSourceSet);
project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> {
EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class);
eclipse.classpath((classpath) -> classpath.getPlusConfigurations()
.add(project.getConfigurations()
.getByName(systemTestSourceSet.getRuntimeClasspathConfigurationName())));
});
}
private SourceSet createSourceSet(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
SourceSet systemTestSourceSet = sourceSets.create(SYSTEM_TEST_SOURCE_SET_NAME);
SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
systemTestSourceSet
.setCompileClasspath(systemTestSourceSet.getCompileClasspath().plus(mainSourceSet.getOutput()));
systemTestSourceSet
.setRuntimeClasspath(systemTestSourceSet.getRuntimeClasspath().plus(mainSourceSet.getOutput()));
return systemTestSourceSet;
}
private void createTestTask(Project project, SourceSet systemTestSourceSet) {
Test systemTest = project.getTasks().create(SYSTEM_TEST_TASK_NAME, Test.class);
systemTest.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
systemTest.setDescription("Runs system tests.");
systemTest.setTestClassesDirs(systemTestSourceSet.getOutput().getClassesDirs());
systemTest.setClasspath(systemTestSourceSet.getRuntimeClasspath());
systemTest.shouldRunAfter(JavaPlugin.TEST_TASK_NAME);
if (isCi()) {
systemTest.getOutputs().upToDateWhen(NEVER);
systemTest.getOutputs().doNotCacheIf("System tests are always rerun on CI", (task) -> true);
}
}
private boolean isCi() {
return Boolean.parseBoolean(System.getenv("CI"));
}
}

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

Loading…
Cancel
Save