17
17
package org .springframework .boot .build .autoconfigure ;
18
18
19
19
import java .io .File ;
20
+ import java .io .IOException ;
21
+ import java .nio .file .Files ;
22
+ import java .nio .file .Path ;
20
23
import java .util .Collections ;
24
+ import java .util .List ;
21
25
import java .util .concurrent .Callable ;
22
26
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 ;
23
33
import org .gradle .api .Plugin ;
24
34
import org .gradle .api .Project ;
25
35
import org .gradle .api .artifacts .Configuration ;
26
36
import org .gradle .api .plugins .JavaPlugin ;
27
37
import org .gradle .api .plugins .JavaPluginExtension ;
38
+ import org .gradle .api .provider .Provider ;
39
+ import org .gradle .api .tasks .PathSensitivity ;
28
40
import org .gradle .api .tasks .SourceSet ;
29
41
30
42
import org .springframework .boot .build .DeployedPlugin ;
43
+ import org .springframework .boot .build .architecture .ArchitectureCheck ;
44
+ import org .springframework .boot .build .architecture .ArchitecturePlugin ;
31
45
import org .springframework .boot .build .context .properties .ConfigurationPropertiesPlugin ;
32
46
33
47
/**
34
48
* {@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 :
37
51
*
38
52
* <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>
43
64
* </ul>
44
65
*
45
66
* @author Andy Wilkinson
@@ -52,6 +73,8 @@ public class AutoConfigurationPlugin implements Plugin<Project> {
52
73
*/
53
74
public static final String AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME = "autoConfigurationMetadata" ;
54
75
76
+ private static final String AUTO_CONFIGURATION_IMPORTS_PATH = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports" ;
77
+
55
78
@ Override
56
79
public void apply (Project project ) {
57
80
project .getPlugins ().apply (DeployedPlugin .class );
@@ -80,7 +103,71 @@ public void apply(Project project) {
80
103
project .provider ((Callable <File >) task ::getOutputFile ),
81
104
(artifact ) -> artifact .builtBy (task ));
82
105
});
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
+ });
83
120
});
84
121
}
85
122
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
+
86
173
}
0 commit comments