Compare commits
No commits in common. 'main' and '2.5.x' have entirely different histories.
@ -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,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-&#36;today.year the original author 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." />
|
||||
<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&&!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
|
@ -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
|
||||
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.build;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.plugins.ide.api.XmlFileContentMerger;
|
||||
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
|
||||
import org.gradle.plugins.ide.eclipse.model.Classpath;
|
||||
import org.gradle.plugins.ide.eclipse.model.ClasspathEntry;
|
||||
import org.gradle.plugins.ide.eclipse.model.EclipseClasspath;
|
||||
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
|
||||
import org.gradle.plugins.ide.eclipse.model.Library;
|
||||
|
||||
/**
|
||||
* Conventions that are applied in the presence of the {@link EclipsePlugin} to work
|
||||
* around buildship issue {@code #1238}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class EclipseConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withType(EclipsePlugin.class, (eclipse) -> {
|
||||
EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class);
|
||||
eclipseModel.classpath(this::configureClasspath);
|
||||
});
|
||||
}
|
||||
|
||||
private void configureClasspath(EclipseClasspath classpath) {
|
||||
classpath.file(this::configureClasspathFile);
|
||||
}
|
||||
|
||||
private void configureClasspathFile(XmlFileContentMerger merger) {
|
||||
merger.whenMerged((content) -> {
|
||||
if (content instanceof Classpath classpath) {
|
||||
classpath.getEntries().removeIf(this::isKotlinPluginContributedBuildDirectory);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isKotlinPluginContributedBuildDirectory(ClasspathEntry entry) {
|
||||
return (entry instanceof Library library) && isKotlinPluginContributedBuildDirectory(library.getPath())
|
||||
&& isTest(library);
|
||||
}
|
||||
|
||||
private boolean isKotlinPluginContributedBuildDirectory(String path) {
|
||||
return path.contains("/main") && (path.contains("/build/classes/") || path.contains("/build/resources/"));
|
||||
}
|
||||
|
||||
private boolean isTest(Library library) {
|
||||
Object value = library.getEntryAttributes().get("test");
|
||||
return (value instanceof String string && Boolean.parseBoolean(string));
|
||||
}
|
||||
|
||||
}
|
@ -1,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,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,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,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,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,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,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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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,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,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,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,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…
Reference in New Issue