Generate the AutoConfiguration.imports file from annotations

This commit adds the `AutoConfigurationImportsAnnotationProcessor` to
the `spring-boot-autoconfigure-processor` annotation processor
module. When added to a project build, the annotation processor will
generate the
`org.springframework.boot.autoconfigure.AutoConfiguration.imports`
file automatically from `@AutoConfiguration`-annotated classes. It
also applies the annotation processor to the Spring Boot build.

Closes gh-31228
This commit is contained in:
Scott Frederick
2022-09-02 13:24:26 -05:00
parent 795ea289db
commit da4de7d67d
16 changed files with 265 additions and 338 deletions

View File

@@ -23,15 +23,13 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import org.gradle.api.DefaultTask;
import org.gradle.api.Task;
@@ -43,16 +41,18 @@ import org.gradle.api.tasks.TaskAction;
import org.springframework.asm.ClassReader;
import org.springframework.asm.Opcodes;
import org.springframework.core.CollectionFactory;
import org.springframework.util.StringUtils;
/**
* A {@link Task} for generating metadata describing a project's auto-configuration
* classes.
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
public class AutoConfigurationMetadata extends DefaultTask {
private static final String IMPORTS_FILE_PATH = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports";
private static final String COMMENT_START = "#";
private SourceSet sourceSet;
@@ -60,13 +60,7 @@ public class AutoConfigurationMetadata extends DefaultTask {
private File outputFile;
public AutoConfigurationMetadata() {
getInputs()
.file((Callable<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring.factories"))
.withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("spring.factories");
getInputs()
.file((Callable<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"))
getInputs().file((Callable<File>) this::findAutoConfigurationImportsFile)
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("org.springframework.boot.autoconfigure.AutoConfiguration");
@@ -99,9 +93,7 @@ public class AutoConfigurationMetadata extends DefaultTask {
private Properties readAutoConfiguration() throws IOException {
Properties autoConfiguration = CollectionFactory.createSortedProperties(true);
Set<String> classNames = new LinkedHashSet<>();
classNames.addAll(readSpringFactories());
classNames.addAll(readAutoConfigurationsFile());
List<String> classNames = readAutoConfigurationsFile();
Set<String> publicClassNames = new LinkedHashSet<>();
for (String className : classNames) {
File classFile = findClassFile(className);
@@ -120,61 +112,41 @@ public class AutoConfigurationMetadata extends DefaultTask {
return autoConfiguration;
}
/**
* Reads auto-configurations from META-INF/spring.factories.
* @return auto-configurations
*/
private Set<String> readSpringFactories() throws IOException {
File file = new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories");
if (!file.exists()) {
return Collections.emptySet();
}
Properties springFactories = readSpringFactories(file);
String enableAutoConfiguration = springFactories
.getProperty("org.springframework.boot.autoconfigure.EnableAutoConfiguration");
return StringUtils.commaDelimitedListToSet(enableAutoConfiguration);
}
/**
* Reads auto-configurations from
* META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
* @return auto-configurations
*/
private List<String> readAutoConfigurationsFile() throws IOException {
File file = new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports");
if (!file.exists()) {
File file = findAutoConfigurationImportsFile();
if (file == null) {
return Collections.emptyList();
}
// Nearly identical copy of
// org.springframework.boot.context.annotation.ImportCandidates.load
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
List<String> autoConfigurations = new ArrayList<>();
String line;
while ((line = reader.readLine()) != null) {
line = stripComment(line);
line = line.trim();
if (line.isEmpty()) {
continue;
}
autoConfigurations.add(line);
}
return autoConfigurations;
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
return reader.lines().map(this::stripComment).filter((line) -> !line.isEmpty())
.collect(Collectors.toList());
}
}
private String stripComment(String line) {
int commentStart = line.indexOf(COMMENT_START);
if (commentStart == -1) {
return line;
return line.trim();
}
return line.substring(0, commentStart);
return line.substring(0, commentStart).trim();
}
private File findAutoConfigurationImportsFile() {
return findFileInClassesDirs(IMPORTS_FILE_PATH);
}
private File findClassFile(String className) {
String classFileName = className.replace(".", "/") + ".class";
return findFileInClassesDirs(className.replace(".", "/") + ".class");
}
private File findFileInClassesDirs(String fileName) {
for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) {
File classFile = new File(classesDir, classFileName);
File classFile = new File(classesDir, fileName);
if (classFile.isFile()) {
return classFile;
}
@@ -182,12 +154,4 @@ public class AutoConfigurationMetadata extends DefaultTask {
return null;
}
private Properties readSpringFactories(File file) throws IOException {
Properties springFactories = new Properties();
try (Reader in = new FileReader(file)) {
springFactories.load(in);
}
return springFactories;
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.boot.build.autoconfigure;
import java.io.File;
import java.util.Collections;
import java.util.concurrent.Callable;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
@@ -43,6 +42,7 @@ import org.springframework.boot.build.context.properties.ConfigurationProperties
* </ul>
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
public class AutoConfigurationPlugin implements Plugin<Project> {
@@ -62,9 +62,6 @@ public class AutoConfigurationPlugin implements Plugin<Project> {
annotationProcessors.getDependencies()
.add(project.getDependencies().project(Collections.singletonMap("path",
":spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-processor")));
annotationProcessors.getDependencies()
.add(project.getDependencies().project(Collections.singletonMap("path",
":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor")));
project.getTasks().create("autoConfigurationMetadata", AutoConfigurationMetadata.class, (task) -> {
SourceSet main = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
@@ -72,7 +69,7 @@ public class AutoConfigurationPlugin implements Plugin<Project> {
task.dependsOn(main.getClassesTaskName());
task.setOutputFile(new File(project.getBuildDir(), "auto-configuration-metadata.properties"));
project.getArtifacts().add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME,
project.provider((Callable<File>) task::getOutputFile), (artifact) -> artifact.builtBy(task));
project.provider(task::getOutputFile), (artifact) -> artifact.builtBy(task));
});
});
}