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