Skip to content

Commit 142a398

Browse files
committed
Check that AutoConfiguration classes are listed in imports file
Closes gh-36253
1 parent 300e507 commit 142a398

File tree

3 files changed

+119
-12
lines changed

3 files changed

+119
-12
lines changed

buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.nio.file.StandardOpenOption;
2424
import java.util.List;
2525
import java.util.stream.Collectors;
26-
import java.util.stream.Stream;
2726

2827
import com.tngtech.archunit.base.DescribedPredicate;
2928
import com.tngtech.archunit.core.domain.JavaClass;
@@ -46,9 +45,12 @@
4645
import org.gradle.api.file.DirectoryProperty;
4746
import org.gradle.api.file.FileCollection;
4847
import org.gradle.api.file.FileTree;
48+
import org.gradle.api.provider.ListProperty;
4949
import org.gradle.api.tasks.IgnoreEmptyDirectories;
50+
import org.gradle.api.tasks.Input;
5051
import org.gradle.api.tasks.InputFiles;
5152
import org.gradle.api.tasks.Internal;
53+
import org.gradle.api.tasks.Optional;
5254
import org.gradle.api.tasks.OutputDirectory;
5355
import org.gradle.api.tasks.PathSensitive;
5456
import org.gradle.api.tasks.PathSensitivity;
@@ -66,17 +68,21 @@ public abstract class ArchitectureCheck extends DefaultTask {
6668

6769
public ArchitectureCheck() {
6870
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
71+
getRules().addAll(allPackagesShouldBeFreeOfTangles(),
72+
allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(),
73+
allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters(),
74+
noClassesShouldCallStepVerifierStepVerifyComplete(),
75+
noClassesShouldConfigureDefaultStepVerifierTimeout());
76+
getRuleDescriptions()
77+
.set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).collect(Collectors.toList())));
6978
}
7079

7180
@TaskAction
7281
void checkArchitecture() throws IOException {
7382
JavaClasses javaClasses = new ClassFileImporter()
7483
.importPaths(this.classes.getFiles().stream().map(File::toPath).collect(Collectors.toList()));
75-
List<EvaluationResult> violations = Stream.of(allPackagesShouldBeFreeOfTangles(),
76-
allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(),
77-
allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters(),
78-
noClassesShouldCallStepVerifierStepVerifyComplete(),
79-
noClassesShouldConfigureDefaultStepVerifierTimeout())
84+
List<EvaluationResult> violations = getRules().get()
85+
.stream()
8086
.map((rule) -> rule.evaluate(javaClasses))
8187
.filter(EvaluationResult::hasViolation)
8288
.collect(Collectors.toList());
@@ -196,7 +202,20 @@ final FileTree getInputClasses() {
196202
return this.classes.getAsFileTree();
197203
}
198204

205+
@Optional
206+
@InputFiles
207+
@PathSensitive(PathSensitivity.RELATIVE)
208+
public abstract DirectoryProperty getResourcesDirectory();
209+
199210
@OutputDirectory
200211
public abstract DirectoryProperty getOutputDirectory();
201212

213+
@Internal
214+
public abstract ListProperty<ArchRule> getRules();
215+
216+
@Input
217+
// The rules themselves can't be an input as they aren't serializable so we use their
218+
// descriptions instead
219+
abstract ListProperty<String> getRuleDescriptions();
220+
202221
}

buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ private void registerTasks(Project project) {
5050
.register("checkArchitecture" + StringUtils.capitalize(sourceSet.getName()), ArchitectureCheck.class,
5151
(task) -> {
5252
task.setClasses(sourceSet.getOutput().getClassesDirs());
53+
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
5354
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
5455
+ " source set.");
5556
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);

buildSrc/src/main/java/org/springframework/boot/build/autoconfigure/AutoConfigurationPlugin.java

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,50 @@
1717
package org.springframework.boot.build.autoconfigure;
1818

1919
import java.io.File;
20+
import java.io.IOException;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
2023
import java.util.Collections;
24+
import java.util.List;
2125
import java.util.concurrent.Callable;
2226

27+
import com.tngtech.archunit.core.domain.JavaClass;
28+
import com.tngtech.archunit.lang.ArchCondition;
29+
import com.tngtech.archunit.lang.ArchRule;
30+
import com.tngtech.archunit.lang.ConditionEvents;
31+
import com.tngtech.archunit.lang.SimpleConditionEvent;
32+
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
2333
import org.gradle.api.Plugin;
2434
import org.gradle.api.Project;
2535
import org.gradle.api.artifacts.Configuration;
2636
import org.gradle.api.plugins.JavaPlugin;
2737
import org.gradle.api.plugins.JavaPluginExtension;
38+
import org.gradle.api.provider.Provider;
39+
import org.gradle.api.tasks.PathSensitivity;
2840
import org.gradle.api.tasks.SourceSet;
2941

3042
import org.springframework.boot.build.DeployedPlugin;
43+
import org.springframework.boot.build.architecture.ArchitectureCheck;
44+
import org.springframework.boot.build.architecture.ArchitecturePlugin;
3145
import org.springframework.boot.build.context.properties.ConfigurationPropertiesPlugin;
3246

3347
/**
3448
* {@link Plugin} for projects that define auto-configuration. When applied, the plugin
35-
* applies the {@link DeployedPlugin}. Additionally, it reacts to the presence of the
36-
* {@link JavaPlugin} by:
49+
* applies the {@link DeployedPlugin}. Additionally, when the {@link JavaPlugin} is
50+
* applied it:
3751
*
3852
* <ul>
39-
* <li>Applying the {@link ConfigurationPropertiesPlugin}.
40-
* <li>Adding a dependency on the auto-configuration annotation processor.
41-
* <li>Defining a task that produces metadata describing the auto-configuration. The
42-
* metadata is made available as an artifact in the
53+
* <li>Applies the {@link ConfigurationPropertiesPlugin}.
54+
* <li>Adds a dependency on the auto-configuration annotation processor.
55+
* <li>Defines a task that produces metadata describing the auto-configuration. The
56+
* metadata is made available as an artifact in the {@code autoConfigurationMetadata}
57+
* configuration.
58+
* <li>Reacts to the {@link ArchitecturePlugin} being applied and:
59+
* <ul>
60+
* <li>Adds a rule to the {@code checkArchitectureMain} task to verify that all
61+
* {@code AutoConfiguration} classes are listed in the {@code AutoConfiguration.imports}
62+
* file.
63+
* </ul>
4364
* </ul>
4465
*
4566
* @author Andy Wilkinson
@@ -52,6 +73,8 @@ public class AutoConfigurationPlugin implements Plugin<Project> {
5273
*/
5374
public static final String AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME = "autoConfigurationMetadata";
5475

76+
private static final String AUTO_CONFIGURATION_IMPORTS_PATH = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports";
77+
5578
@Override
5679
public void apply(Project project) {
5780
project.getPlugins().apply(DeployedPlugin.class);
@@ -80,7 +103,71 @@ public void apply(Project project) {
80103
project.provider((Callable<File>) task::getOutputFile),
81104
(artifact) -> artifact.builtBy(task));
82105
});
106+
project.getPlugins().withType(ArchitecturePlugin.class, (architecturePlugin) -> {
107+
project.getTasks().named("checkArchitectureMain", ArchitectureCheck.class).configure((task) -> {
108+
SourceSet main = project.getExtensions()
109+
.getByType(JavaPluginExtension.class)
110+
.getSourceSets()
111+
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
112+
File resourcesDirectory = main.getOutput().getResourcesDir();
113+
task.dependsOn(main.getProcessResourcesTaskName());
114+
task.getInputs().files(resourcesDirectory).optional().withPathSensitivity(PathSensitivity.RELATIVE);
115+
task.getRules()
116+
.add(allClassesAnnotatedWithAutoConfigurationShouldBeListedInAutoConfigurationImports(
117+
autoConfigurationImports(project, resourcesDirectory)));
118+
});
119+
});
83120
});
84121
}
85122

123+
private ArchRule allClassesAnnotatedWithAutoConfigurationShouldBeListedInAutoConfigurationImports(
124+
Provider<AutoConfigurationImports> imports) {
125+
return ArchRuleDefinition.classes()
126+
.that()
127+
.areAnnotatedWith("org.springframework.boot.autoconfigure.AutoConfiguration")
128+
.should(beListedInAutoConfigurationImports(imports))
129+
.allowEmptyShould(true);
130+
}
131+
132+
private ArchCondition<JavaClass> beListedInAutoConfigurationImports(Provider<AutoConfigurationImports> imports) {
133+
return new ArchCondition<JavaClass>("be listed in " + AUTO_CONFIGURATION_IMPORTS_PATH) {
134+
135+
@Override
136+
public void check(JavaClass item, ConditionEvents events) {
137+
AutoConfigurationImports autoConfigurationImports = imports.get();
138+
if (!autoConfigurationImports.imports.contains(item.getName())) {
139+
events.add(SimpleConditionEvent.violated(item,
140+
item.getName() + " was not listed in " + autoConfigurationImports.importsFile));
141+
}
142+
}
143+
144+
};
145+
}
146+
147+
private Provider<AutoConfigurationImports> autoConfigurationImports(Project project, File resourcesDirectory) {
148+
Path importsFile = new File(resourcesDirectory, AUTO_CONFIGURATION_IMPORTS_PATH).toPath();
149+
return project.provider(() -> {
150+
try {
151+
return new AutoConfigurationImports(project.getProjectDir().toPath().relativize(importsFile),
152+
Files.readAllLines(importsFile));
153+
}
154+
catch (IOException ex) {
155+
throw new RuntimeException("Failed to read AutoConfiguration.imports", ex);
156+
}
157+
});
158+
}
159+
160+
private static final class AutoConfigurationImports {
161+
162+
private final Path importsFile;
163+
164+
private final List<String> imports;
165+
166+
private AutoConfigurationImports(Path importsFile, List<String> imports) {
167+
this.importsFile = importsFile;
168+
this.imports = imports;
169+
}
170+
171+
}
172+
86173
}

0 commit comments

Comments
 (0)