commit
b61578d183
|
@ -32,6 +32,7 @@ import io.spring.javaformat.gradle.tasks.CheckFormat;
|
|||
import io.spring.javaformat.gradle.tasks.Format;
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
|
@ -54,6 +55,8 @@ import org.gradle.external.javadoc.CoreJavadocOptions;
|
|||
import org.springframework.boot.build.architecture.ArchitecturePlugin;
|
||||
import org.springframework.boot.build.classpath.CheckClasspathForProhibitedDependencies;
|
||||
import org.springframework.boot.build.optional.OptionalDependenciesPlugin;
|
||||
import org.springframework.boot.build.springframework.CheckAotFactories;
|
||||
import org.springframework.boot.build.springframework.CheckSpringFactories;
|
||||
import org.springframework.boot.build.testing.TestFailuresPlugin;
|
||||
import org.springframework.boot.build.toolchain.ToolchainPlugin;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -98,6 +101,19 @@ import org.springframework.util.StringUtils;
|
|||
* <li>{@code Implementation-Version}
|
||||
* </ul>
|
||||
* <li>{@code spring-boot-parent} is used for dependency management</li>
|
||||
* <li>Additional checks are configured:
|
||||
* <ul>
|
||||
* <li>For all source sets:
|
||||
* <ul>
|
||||
* <li>Prohibited dependencies on the compile classpath
|
||||
* <li>Prohibited dependencies on the runtime classpath
|
||||
* </ul>
|
||||
* <li>For the {@code main} source set:
|
||||
* <ul>
|
||||
* <li>{@code META-INF/spring/aot.factories}
|
||||
* <li>{@code META-INF/spring.factories}
|
||||
* </ul>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*
|
||||
* <p/>
|
||||
|
@ -123,6 +139,7 @@ class JavaConventions {
|
|||
configureDependencyManagement(project);
|
||||
configureToolchain(project);
|
||||
configureProhibitedDependencyChecks(project);
|
||||
configureFactoriesFilesChecks(project);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -304,4 +321,26 @@ class JavaConventions {
|
|||
project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForProhibitedDependencies);
|
||||
}
|
||||
|
||||
private void configureFactoriesFilesChecks(Project project) {
|
||||
SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
|
||||
sourceSets.matching((sourceSet) -> SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName()))
|
||||
.configureEach((main) -> {
|
||||
TaskProvider<Task> check = project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME);
|
||||
TaskProvider<CheckAotFactories> checkAotFactories = project.getTasks()
|
||||
.register("checkAotFactories", CheckAotFactories.class, (task) -> {
|
||||
task.setSource(main.getResources());
|
||||
task.setClasspath(main.getOutput().getClassesDirs());
|
||||
task.setDescription("Checks the META-INF/spring/aot.factories file of the main source set.");
|
||||
});
|
||||
check.configure((task) -> task.dependsOn(checkAotFactories));
|
||||
TaskProvider<CheckSpringFactories> checkSpringFactories = project.getTasks()
|
||||
.register("checkSpringFactories", CheckSpringFactories.class, (task) -> {
|
||||
task.setSource(main.getResources());
|
||||
task.setClasspath(main.getOutput().getClassesDirs());
|
||||
task.setDescription("Checks the META-INF/spring.factories file of the main source set.");
|
||||
});
|
||||
check.configure((task) -> task.dependsOn(checkSpringFactories));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2012-2025 the original author 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.springframework;
|
||||
|
||||
import org.gradle.api.Task;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks {@code META-INF/spring/aot.factories}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class CheckAotFactories extends CheckFactoriesFile {
|
||||
|
||||
public CheckAotFactories() {
|
||||
super("META-INF/spring/aot.factories");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright 2012-2025 the original author 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.springframework;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
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.tasks.Classpath;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
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;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks files loaded by {@link SpringFactoriesLoader}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class CheckFactoriesFile extends DefaultTask {
|
||||
|
||||
private final String path;
|
||||
|
||||
private FileCollection sourceFiles = getProject().getObjects().fileCollection();
|
||||
|
||||
private FileCollection classpath = getProject().getObjects().fileCollection();
|
||||
|
||||
protected CheckFactoriesFile(String path) {
|
||||
this.path = path;
|
||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||
setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
@SkipWhenEmpty
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public FileTree getSource() {
|
||||
return this.sourceFiles.getAsFileTree().matching((filter) -> filter.include(this.path));
|
||||
}
|
||||
|
||||
public void setSource(Object source) {
|
||||
this.sourceFiles = getProject().getObjects().fileCollection().from(source);
|
||||
}
|
||||
|
||||
@Classpath
|
||||
public FileCollection getClasspath() {
|
||||
return this.classpath;
|
||||
}
|
||||
|
||||
public void setClasspath(Object classpath) {
|
||||
this.classpath = getProject().getObjects().fileCollection().from(classpath);
|
||||
}
|
||||
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutputDirectory();
|
||||
|
||||
@TaskAction
|
||||
void execute() {
|
||||
getSource().forEach(this::check);
|
||||
}
|
||||
|
||||
private void check(File factoriesFile) {
|
||||
Properties factories = load(factoriesFile);
|
||||
Map<String, List<String>> problems = new LinkedHashMap<>();
|
||||
for (String key : factories.stringPropertyNames()) {
|
||||
List<String> values = Arrays
|
||||
.asList(StringUtils.commaDelimitedListToStringArray(factories.getProperty(key)));
|
||||
for (String value : values) {
|
||||
boolean found = find(value);
|
||||
if (!found) {
|
||||
List<String> problemsForKey = problems.computeIfAbsent(key, (k) -> new ArrayList<>());
|
||||
String binaryName = binaryNameOf(value);
|
||||
found = find(binaryName);
|
||||
if (found) {
|
||||
problemsForKey
|
||||
.add("'%s' should be listed using its binary name '%s'".formatted(value, binaryName));
|
||||
}
|
||||
else {
|
||||
problemsForKey.add("'%s' was not found".formatted(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
List<String> sortedValues = new ArrayList<>(values);
|
||||
Collections.sort(sortedValues);
|
||||
if (!sortedValues.equals(values)) {
|
||||
List<String> problemsForKey = problems.computeIfAbsent(key, (k) -> new ArrayList<>());
|
||||
problemsForKey.add("Entries should be sorted alphabetically");
|
||||
}
|
||||
}
|
||||
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
|
||||
writeReport(factoriesFile, problems, outputFile);
|
||||
if (!problems.isEmpty()) {
|
||||
throw new GradleException("%s check failed. See '%s' for details".formatted(this.path, outputFile));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean find(String className) {
|
||||
for (File root : this.classpath.getFiles()) {
|
||||
String classFilePath = className.replace(".", "/") + ".class";
|
||||
if (new File(root, classFilePath).isFile()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String binaryNameOf(String className) {
|
||||
int lastDotIndex = className.lastIndexOf('.');
|
||||
return className.substring(0, lastDotIndex) + "$" + className.substring(lastDotIndex + 1);
|
||||
}
|
||||
|
||||
private Properties load(File aotFactories) {
|
||||
Properties properties = new Properties();
|
||||
try (FileInputStream input = new FileInputStream(aotFactories)) {
|
||||
properties.load(input);
|
||||
return properties;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeReport(File factoriesFile, Map<String, List<String>> problems, File outputFile) {
|
||||
outputFile.getParentFile().mkdirs();
|
||||
StringBuilder report = new StringBuilder();
|
||||
if (!problems.isEmpty()) {
|
||||
report.append("Found problems in '%s':%n".formatted(factoriesFile));
|
||||
problems.forEach((key, problemsForKey) -> {
|
||||
report.append(" - %s:%n".formatted(key));
|
||||
problemsForKey.forEach((problem) -> report.append(" - %s%n".formatted(problem)));
|
||||
});
|
||||
}
|
||||
try {
|
||||
Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2012-2025 the original author 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.springframework;
|
||||
|
||||
import org.gradle.api.Task;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks {@code META-INF/spring.factories}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class CheckSpringFactories extends CheckFactoriesFile {
|
||||
|
||||
public CheckSpringFactories() {
|
||||
super("META-INF/spring.factories");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.ServiceLevelObjectiveBoundary.ServiceLevelObjectiveBoundaryHints
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.ServiceLevelObjectiveBoundary$ServiceLevelObjectiveBoundaryHints
|
||||
|
|
|
@ -38,8 +38,8 @@ org.springframework.boot.autoconfigure.ssl.BundleContentNotWatchableFailureAnaly
|
|||
# Template Availability Providers
|
||||
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
|
||||
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
|
||||
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
|
||||
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
|
||||
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
|
||||
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
|
||||
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider.FreeMarkerTemplateAvailabilityRuntimeHints,\
|
||||
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider.GroovyTemplateAvailabilityRuntimeHints,\
|
||||
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.JacksonAutoConfigurationRuntimeHints,\
|
||||
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider$FreeMarkerTemplateAvailabilityRuntimeHints,\
|
||||
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider$GroovyTemplateAvailabilityRuntimeHints,\
|
||||
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonAutoConfigurationRuntimeHints,\
|
||||
org.springframework.boot.autoconfigure.template.TemplateRuntimeHints
|
||||
|
||||
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
|
||||
|
|
|
@ -4,8 +4,8 @@ org.springframework.boot.devtools.restart.RestartScopeInitializer
|
|||
|
||||
# Application Listeners
|
||||
org.springframework.context.ApplicationListener=\
|
||||
org.springframework.boot.devtools.restart.RestartApplicationListener,\
|
||||
org.springframework.boot.devtools.logger.DevToolsLogFactory.Listener
|
||||
org.springframework.boot.devtools.logger.DevToolsLogFactory$Listener,\
|
||||
org.springframework.boot.devtools.restart.RestartApplicationListener
|
||||
|
||||
# Environment Post Processors
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
|
||||
org.springframework.boot.testcontainers.service.connection.ConnectionDetailsRegistrar.ServiceConnectionBeanRegistrationExcludeFilter
|
||||
org.springframework.boot.testcontainers.service.connection.ConnectionDetailsRegistrar$ServiceConnectionBeanRegistrationExcludeFilter
|
||||
|
||||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory.ContainerConnectionDetailsFactoriesRuntimeHints
|
||||
org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory$ContainerConnectionDetailsFactoriesRuntimeHints
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Logging Systems
|
||||
org.springframework.boot.logging.LoggingSystemFactory=\
|
||||
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
|
||||
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
|
||||
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
|
||||
org.springframework.boot.logging.java.JavaLoggingSystem$Factory,\
|
||||
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem$Factory,\
|
||||
org.springframework.boot.logging.logback.LogbackLoggingSystem$Factory
|
||||
|
||||
# PropertySource Loaders
|
||||
org.springframework.boot.env.PropertySourceLoader=\
|
||||
|
@ -99,10 +99,10 @@ org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializerDetector
|
|||
|
||||
# Depends On Database Initialization Detectors
|
||||
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
|
||||
org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector,\
|
||||
org.springframework.boot.jdbc.SpringJdbcDependsOnDatabaseInitializationDetector,\
|
||||
org.springframework.boot.jooq.JooqDependsOnDatabaseInitializationDetector,\
|
||||
org.springframework.boot.orm.jpa.JpaDependsOnDatabaseInitializationDetector
|
||||
org.springframework.boot.orm.jpa.JpaDependsOnDatabaseInitializationDetector,\
|
||||
org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector
|
||||
|
||||
# Resource Locator Protocol Resolvers
|
||||
org.springframework.core.io.ProtocolResolver=\
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||
org.springframework.boot.ApplicationProperties.ApplicationPropertiesRuntimeHints,\
|
||||
org.springframework.boot.SpringApplicationBannerPrinter.SpringApplicationBannerPrinterRuntimeHints,\
|
||||
org.springframework.boot.WebApplicationType.WebApplicationTypeRuntimeHints,\
|
||||
org.springframework.boot.ApplicationProperties$ApplicationPropertiesRuntimeHints,\
|
||||
org.springframework.boot.SpringApplicationBannerPrinter$SpringApplicationBannerPrinterRuntimeHints,\
|
||||
org.springframework.boot.WebApplicationType$WebApplicationTypeRuntimeHints,\
|
||||
org.springframework.boot.context.config.ConfigDataLocationRuntimeHints,\
|
||||
org.springframework.boot.context.config.ConfigDataPropertiesRuntimeHints,\
|
||||
org.springframework.boot.env.PropertySourceRuntimeHints,\
|
||||
|
@ -10,16 +10,16 @@ org.springframework.boot.jdbc.DataSourceBuilderRuntimeHints,\
|
|||
org.springframework.boot.json.JacksonRuntimeHints,\
|
||||
org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\
|
||||
org.springframework.boot.logging.logback.LogbackRuntimeHints,\
|
||||
org.springframework.boot.logging.structured.ElasticCommonSchemaProperties.ElasticCommonSchemaPropertiesRuntimeHints,\
|
||||
org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties.GraylogExtendedLogFormatPropertiesRuntimeHints,\
|
||||
org.springframework.boot.logging.structured.StructuredLoggingJsonProperties.StructuredLoggingJsonPropertiesRuntimeHints,\
|
||||
org.springframework.boot.web.embedded.undertow.UndertowWebServer.UndertowWebServerRuntimeHints,\
|
||||
org.springframework.boot.web.server.MimeMappings.MimeMappingsRuntimeHints
|
||||
org.springframework.boot.logging.structured.ElasticCommonSchemaProperties$ElasticCommonSchemaPropertiesRuntimeHints,\
|
||||
org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties$GraylogExtendedLogFormatPropertiesRuntimeHints,\
|
||||
org.springframework.boot.logging.structured.StructuredLoggingJsonProperties$StructuredLoggingJsonPropertiesRuntimeHints,\
|
||||
org.springframework.boot.web.embedded.undertow.UndertowWebServer$UndertowWebServerRuntimeHints,\
|
||||
org.springframework.boot.web.server.MimeMappings$MimeMappingsRuntimeHints
|
||||
|
||||
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
|
||||
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor,\
|
||||
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.EnvironmentBeanFactoryInitializationAotProcessor,\
|
||||
org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor,\
|
||||
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener$EnvironmentBeanFactoryInitializationAotProcessor,\
|
||||
org.springframework.boot.jackson.JsonComponentModule$JsonComponentBeanFactoryInitializationAotProcessor,\
|
||||
org.springframework.boot.logging.structured.StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor
|
||||
|
||||
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
|
||||
|
|
Loading…
Reference in New Issue