commit
						7088d0e04a
					
				|  | @ -22,7 +22,6 @@ import java.nio.file.Files; | ||||||
| import java.nio.file.StandardOpenOption; | import java.nio.file.StandardOpenOption; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| import java.util.stream.Stream; |  | ||||||
| 
 | 
 | ||||||
| import com.tngtech.archunit.base.DescribedPredicate; | import com.tngtech.archunit.base.DescribedPredicate; | ||||||
| import com.tngtech.archunit.core.domain.JavaClass; | import com.tngtech.archunit.core.domain.JavaClass; | ||||||
|  | @ -45,9 +44,12 @@ import org.gradle.api.Task; | ||||||
| import org.gradle.api.file.DirectoryProperty; | import org.gradle.api.file.DirectoryProperty; | ||||||
| import org.gradle.api.file.FileCollection; | import org.gradle.api.file.FileCollection; | ||||||
| import org.gradle.api.file.FileTree; | import org.gradle.api.file.FileTree; | ||||||
|  | import org.gradle.api.provider.ListProperty; | ||||||
| import org.gradle.api.tasks.IgnoreEmptyDirectories; | import org.gradle.api.tasks.IgnoreEmptyDirectories; | ||||||
|  | import org.gradle.api.tasks.Input; | ||||||
| import org.gradle.api.tasks.InputFiles; | import org.gradle.api.tasks.InputFiles; | ||||||
| import org.gradle.api.tasks.Internal; | import org.gradle.api.tasks.Internal; | ||||||
|  | import org.gradle.api.tasks.Optional; | ||||||
| import org.gradle.api.tasks.OutputDirectory; | import org.gradle.api.tasks.OutputDirectory; | ||||||
| import org.gradle.api.tasks.PathSensitive; | import org.gradle.api.tasks.PathSensitive; | ||||||
| import org.gradle.api.tasks.PathSensitivity; | import org.gradle.api.tasks.PathSensitivity; | ||||||
|  | @ -65,17 +67,20 @@ public abstract class ArchitectureCheck extends DefaultTask { | ||||||
| 
 | 
 | ||||||
| 	public ArchitectureCheck() { | 	public ArchitectureCheck() { | ||||||
| 		getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); | 		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 | 	@TaskAction | ||||||
| 	void checkArchitecture() throws IOException { | 	void checkArchitecture() throws IOException { | ||||||
| 		JavaClasses javaClasses = new ClassFileImporter() | 		JavaClasses javaClasses = new ClassFileImporter() | ||||||
| 			.importPaths(this.classes.getFiles().stream().map(File::toPath).toList()); | 			.importPaths(this.classes.getFiles().stream().map(File::toPath).toList()); | ||||||
| 		List<EvaluationResult> violations = Stream.of(allPackagesShouldBeFreeOfTangles(), | 		List<EvaluationResult> violations = getRules().get() | ||||||
| 				allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(), | 			.stream() | ||||||
| 				allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters(), |  | ||||||
| 				noClassesShouldCallStepVerifierStepVerifyComplete(), |  | ||||||
| 				noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList()) |  | ||||||
| 			.map((rule) -> rule.evaluate(javaClasses)) | 			.map((rule) -> rule.evaluate(javaClasses)) | ||||||
| 			.filter(EvaluationResult::hasViolation) | 			.filter(EvaluationResult::hasViolation) | ||||||
| 			.toList(); | 			.toList(); | ||||||
|  | @ -202,7 +207,20 @@ public abstract class ArchitectureCheck extends DefaultTask { | ||||||
| 		return this.classes.getAsFileTree(); | 		return this.classes.getAsFileTree(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Optional | ||||||
|  | 	@InputFiles | ||||||
|  | 	@PathSensitive(PathSensitivity.RELATIVE) | ||||||
|  | 	public abstract DirectoryProperty getResourcesDirectory(); | ||||||
|  | 
 | ||||||
| 	@OutputDirectory | 	@OutputDirectory | ||||||
| 	public abstract DirectoryProperty getOutputDirectory(); | 	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(); | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -50,6 +50,7 @@ public class ArchitecturePlugin implements Plugin<Project> { | ||||||
| 				.register("checkArchitecture" + StringUtils.capitalize(sourceSet.getName()), ArchitectureCheck.class, | 				.register("checkArchitecture" + StringUtils.capitalize(sourceSet.getName()), ArchitectureCheck.class, | ||||||
| 						(task) -> { | 						(task) -> { | ||||||
| 							task.setClasses(sourceSet.getOutput().getClassesDirs()); | 							task.setClasses(sourceSet.getOutput().getClassesDirs()); | ||||||
|  | 							task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir()); | ||||||
| 							task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName() | 							task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName() | ||||||
| 									+ " source set."); | 									+ " source set."); | ||||||
| 							task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); | 							task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); | ||||||
|  |  | ||||||
|  | @ -17,27 +17,48 @@ | ||||||
| package org.springframework.boot.build.autoconfigure; | package org.springframework.boot.build.autoconfigure; | ||||||
| 
 | 
 | ||||||
| import java.io.File; | import java.io.File; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.nio.file.Files; | ||||||
|  | import java.nio.file.Path; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
| import java.util.concurrent.Callable; | import java.util.concurrent.Callable; | ||||||
| 
 | 
 | ||||||
|  | import com.tngtech.archunit.core.domain.JavaClass; | ||||||
|  | import com.tngtech.archunit.lang.ArchCondition; | ||||||
|  | import com.tngtech.archunit.lang.ArchRule; | ||||||
|  | import com.tngtech.archunit.lang.ConditionEvents; | ||||||
|  | import com.tngtech.archunit.lang.SimpleConditionEvent; | ||||||
|  | import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; | ||||||
| import org.gradle.api.Plugin; | import org.gradle.api.Plugin; | ||||||
| import org.gradle.api.Project; | import org.gradle.api.Project; | ||||||
| import org.gradle.api.artifacts.Configuration; | import org.gradle.api.artifacts.Configuration; | ||||||
| import org.gradle.api.plugins.JavaPlugin; | import org.gradle.api.plugins.JavaPlugin; | ||||||
| import org.gradle.api.plugins.JavaPluginExtension; | import org.gradle.api.plugins.JavaPluginExtension; | ||||||
|  | import org.gradle.api.provider.Provider; | ||||||
|  | import org.gradle.api.tasks.PathSensitivity; | ||||||
| import org.gradle.api.tasks.SourceSet; | import org.gradle.api.tasks.SourceSet; | ||||||
| 
 | 
 | ||||||
| import org.springframework.boot.build.DeployedPlugin; | import org.springframework.boot.build.DeployedPlugin; | ||||||
|  | import org.springframework.boot.build.architecture.ArchitectureCheck; | ||||||
|  | import org.springframework.boot.build.architecture.ArchitecturePlugin; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * {@link Plugin} for projects that define auto-configuration. When applied, the plugin |  * {@link Plugin} for projects that define auto-configuration. When applied, the plugin | ||||||
|  * applies the {@link DeployedPlugin}. Additionally, it reacts to the presence of the |  * applies the {@link DeployedPlugin}. Additionally, when the {@link JavaPlugin} is | ||||||
|  * {@link JavaPlugin} by: |  * applied it: | ||||||
|  * |  * | ||||||
|  * <ul> |  * <ul> | ||||||
|  * <li>Adding a dependency on the auto-configuration annotation processor. |  * <li>Adds a dependency on the auto-configuration annotation processor. | ||||||
|  * <li>Defining a task that produces metadata describing the auto-configuration. The |  * <li>Defines a task that produces metadata describing the auto-configuration. The | ||||||
|  * metadata is made available as an artifact in the |  * metadata is made available as an artifact in the {@code autoConfigurationMetadata} | ||||||
|  |  * configuration. | ||||||
|  |  * <li>Reacts to the {@link ArchitecturePlugin} being applied and: | ||||||
|  |  * <ul> | ||||||
|  |  * <li>Adds a rule to the {@code checkArchitectureMain} task to verify that all | ||||||
|  |  * {@code AutoConfiguration} classes are listed in the {@code AutoConfiguration.imports} | ||||||
|  |  * file. | ||||||
|  |  * </ul> | ||||||
|  * </ul> |  * </ul> | ||||||
|  * |  * | ||||||
|  * @author Andy Wilkinson |  * @author Andy Wilkinson | ||||||
|  | @ -50,6 +71,8 @@ public class AutoConfigurationPlugin implements Plugin<Project> { | ||||||
| 	 */ | 	 */ | ||||||
| 	public static final String AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME = "autoConfigurationMetadata"; | 	public static final String AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME = "autoConfigurationMetadata"; | ||||||
| 
 | 
 | ||||||
|  | 	private static final String AUTO_CONFIGURATION_IMPORTS_PATH = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"; | ||||||
|  | 
 | ||||||
| 	@Override | 	@Override | ||||||
| 	public void apply(Project project) { | 	public void apply(Project project) { | ||||||
| 		project.getPlugins().apply(DeployedPlugin.class); | 		project.getPlugins().apply(DeployedPlugin.class); | ||||||
|  | @ -77,7 +100,62 @@ public class AutoConfigurationPlugin implements Plugin<Project> { | ||||||
| 							project.provider((Callable<File>) task::getOutputFile), | 							project.provider((Callable<File>) task::getOutputFile), | ||||||
| 							(artifact) -> artifact.builtBy(task)); | 							(artifact) -> artifact.builtBy(task)); | ||||||
| 			}); | 			}); | ||||||
|  | 			project.getPlugins().withType(ArchitecturePlugin.class, (architecturePlugin) -> { | ||||||
|  | 				project.getTasks().named("checkArchitectureMain", ArchitectureCheck.class).configure((task) -> { | ||||||
|  | 					SourceSet main = project.getExtensions() | ||||||
|  | 						.getByType(JavaPluginExtension.class) | ||||||
|  | 						.getSourceSets() | ||||||
|  | 						.getByName(SourceSet.MAIN_SOURCE_SET_NAME); | ||||||
|  | 					File resourcesDirectory = main.getOutput().getResourcesDir(); | ||||||
|  | 					task.dependsOn(main.getProcessResourcesTaskName()); | ||||||
|  | 					task.getInputs().files(resourcesDirectory).optional().withPathSensitivity(PathSensitivity.RELATIVE); | ||||||
|  | 					task.getRules() | ||||||
|  | 						.add(allClassesAnnotatedWithAutoConfigurationShouldBeListedInAutoConfigurationImports( | ||||||
|  | 								autoConfigurationImports(project, resourcesDirectory))); | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	private ArchRule allClassesAnnotatedWithAutoConfigurationShouldBeListedInAutoConfigurationImports( | ||||||
|  | 			Provider<AutoConfigurationImports> imports) { | ||||||
|  | 		return ArchRuleDefinition.classes() | ||||||
|  | 			.that() | ||||||
|  | 			.areAnnotatedWith("org.springframework.boot.autoconfigure.AutoConfiguration") | ||||||
|  | 			.should(beListedInAutoConfigurationImports(imports)) | ||||||
|  | 			.allowEmptyShould(true); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private ArchCondition<JavaClass> beListedInAutoConfigurationImports(Provider<AutoConfigurationImports> imports) { | ||||||
|  | 		return new ArchCondition<>("be listed in " + AUTO_CONFIGURATION_IMPORTS_PATH) { | ||||||
|  | 
 | ||||||
|  | 			@Override | ||||||
|  | 			public void check(JavaClass item, ConditionEvents events) { | ||||||
|  | 				AutoConfigurationImports autoConfigurationImports = imports.get(); | ||||||
|  | 				if (!autoConfigurationImports.imports.contains(item.getName())) { | ||||||
|  | 					events.add(SimpleConditionEvent.violated(item, | ||||||
|  | 							item.getName() + " was not listed in " + autoConfigurationImports.importsFile)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private Provider<AutoConfigurationImports> autoConfigurationImports(Project project, File resourcesDirectory) { | ||||||
|  | 		Path importsFile = new File(resourcesDirectory, AUTO_CONFIGURATION_IMPORTS_PATH).toPath(); | ||||||
|  | 		return project.provider(() -> { | ||||||
|  | 			try { | ||||||
|  | 				return new AutoConfigurationImports(project.getProjectDir().toPath().relativize(importsFile), | ||||||
|  | 						Files.readAllLines(importsFile)); | ||||||
|  | 			} | ||||||
|  | 			catch (IOException ex) { | ||||||
|  | 				throw new RuntimeException("Failed to read AutoConfiguration.imports", ex); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private static record AutoConfigurationImports(Path importsFile, List<String> imports) { | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue