mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Extend ArchitectureCheck with NullMarkedExtension
Introduce NullMarkedExtension for ArchitectureCheck, which provides functionality to configure packages to ignore in nullability checks and to enable or disable the extension. See gh-47596 Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
committed by
Stéphane Nicoll
parent
a043867ae1
commit
bc2ca5b9fc
+7
-3
@@ -44,6 +44,7 @@ import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.IgnoreEmptyDirectories;
|
||||
import org.gradle.api.tasks.Input;
|
||||
@@ -80,8 +81,8 @@ public abstract class ArchitectureCheck extends DefaultTask {
|
||||
getRules().addAll(ArchitectureRules.standard());
|
||||
getRules().addAll(whenMainSources(
|
||||
() -> Collections.singletonList(ArchitectureRules.allBeanMethodsShouldReturnNonPrivateType())));
|
||||
getRules().addAll(and(getNullMarked(), isMainSourceSet()).map(whenTrue(
|
||||
() -> Collections.singletonList(ArchitectureRules.packagesShouldBeAnnotatedWithNullMarked()))));
|
||||
getRules().addAll(and(getNullMarkedEnabled(), isMainSourceSet()).map(whenTrue(() -> Collections.singletonList(
|
||||
ArchitectureRules.packagesShouldBeAnnotatedWithNullMarked(getNullMarkedIgnoredPackages().get())))));
|
||||
getRuleDescriptions().set(getRules().map(this::asDescriptions));
|
||||
}
|
||||
|
||||
@@ -196,6 +197,9 @@ public abstract class ArchitectureCheck extends DefaultTask {
|
||||
abstract ListProperty<String> getRuleDescriptions();
|
||||
|
||||
@Internal
|
||||
abstract Property<Boolean> getNullMarked();
|
||||
abstract Property<Boolean> getNullMarkedEnabled();
|
||||
|
||||
@Internal
|
||||
abstract SetProperty<String> getNullMarkedIgnoredPackages();
|
||||
|
||||
}
|
||||
|
||||
+49
-6
@@ -16,8 +16,14 @@
|
||||
|
||||
package org.springframework.boot.build.architecture;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
|
||||
/**
|
||||
* Extension to configure the {@link ArchitecturePlugin}.
|
||||
@@ -26,14 +32,51 @@ import org.jspecify.annotations.NullMarked;
|
||||
*/
|
||||
public abstract class ArchitectureCheckExtension {
|
||||
|
||||
public ArchitectureCheckExtension() {
|
||||
getNullMarked().convention(true);
|
||||
private final NullMarkedExtension nullMarked;
|
||||
|
||||
@Inject
|
||||
public ArchitectureCheckExtension(ObjectFactory objects) {
|
||||
this.nullMarked = objects.newInstance(NullMarkedExtension.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this project uses JSpecify's {@link NullMarked} annotations.
|
||||
* @return whether this project uses JSpecify's @NullMarked annotations
|
||||
* Get the {@code NullMarked} extension.
|
||||
* @return the {@code NullMarked} extension
|
||||
*/
|
||||
public abstract Property<Boolean> getNullMarked();
|
||||
public NullMarkedExtension getNullMarked() {
|
||||
return this.nullMarked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@code NullMarked} extension.
|
||||
* @param action the action to configure the {@code NullMarked} extension with
|
||||
*/
|
||||
public void nullMarked(Action<? super NullMarkedExtension> action) {
|
||||
action.execute(this.nullMarked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension to configure the {@code NullMarked} extension.
|
||||
*/
|
||||
public abstract static class NullMarkedExtension {
|
||||
|
||||
public NullMarkedExtension() {
|
||||
getEnabled().convention(true);
|
||||
getIgnoredPackages().convention(new LinkedHashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this project uses JSpecify's {@code NullMarked} annotations.
|
||||
* @return whether this project uses JSpecify's @NullMarked annotations
|
||||
*/
|
||||
public abstract Property<Boolean> getEnabled();
|
||||
|
||||
/**
|
||||
* Packages that should be ignored by the {@code NullMarked} checker.
|
||||
* @return the ignored packages
|
||||
*/
|
||||
public abstract SetProperty<String> getIgnoredPackages();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -59,7 +59,8 @@ public class ArchitecturePlugin implements Plugin<Project> {
|
||||
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
|
||||
+ " source set.");
|
||||
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
task.getNullMarked().set(extension.getNullMarked());
|
||||
task.getNullMarkedEnabled().set(extension.getNullMarked().getEnabled());
|
||||
task.getNullMarkedIgnoredPackages().set(extension.getNullMarked().getIgnoredPackages());
|
||||
});
|
||||
packageTangleChecks.add(checkPackageTangles);
|
||||
}
|
||||
|
||||
+2
-7
@@ -82,11 +82,6 @@ final class ArchitectureRules {
|
||||
|
||||
private static final String TEST_AUTOCONFIGURATION_ANNOTATION = "org.springframework.boot.test.autoconfigure.TestAutoConfiguration";
|
||||
|
||||
private static final Predicate<JavaPackage> NULL_MARKED_PACKAGE_FILTER = (candidate) -> !List
|
||||
.of("org.springframework.boot.cli.json", "org.springframework.boot.configurationmetadata.json",
|
||||
"org.springframework.boot.configurationprocessor.json")
|
||||
.contains(candidate.getName());
|
||||
|
||||
private ArchitectureRules() {
|
||||
}
|
||||
|
||||
@@ -262,8 +257,8 @@ final class ArchitectureRules {
|
||||
.allowEmptyShould(true);
|
||||
}
|
||||
|
||||
static ArchRule packagesShouldBeAnnotatedWithNullMarked() {
|
||||
return ArchRuleDefinition.all(packages(NULL_MARKED_PACKAGE_FILTER))
|
||||
static ArchRule packagesShouldBeAnnotatedWithNullMarked(Set<String> ignoredPackages) {
|
||||
return ArchRuleDefinition.all(packages((javaPackage) -> !ignoredPackages.contains(javaPackage.getName())))
|
||||
.should(beAnnotatedWithNullMarked())
|
||||
.allowEmptyShould(true);
|
||||
}
|
||||
|
||||
+62
-14
@@ -26,6 +26,8 @@ import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
@@ -40,6 +42,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -65,7 +68,7 @@ class ArchitectureCheckTests {
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir Path projectDir) {
|
||||
this.gradleBuild = new GradleBuild(projectDir).withNullMarked(false);
|
||||
this.gradleBuild = new GradleBuild(projectDir).withNullMarkedEnabled(false);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@@ -275,14 +278,23 @@ class ArchitectureCheckTests {
|
||||
@Test
|
||||
void whenPackageIsNotAnnotatedWithNullMarkedWithMainSourcesShouldFailAndWriteEmptyReport() throws IOException {
|
||||
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "nullmarked/notannotated");
|
||||
buildAndFail(this.gradleBuild.withNullMarked(true), Task.CHECK_ARCHITECTURE_MAIN,
|
||||
buildAndFail(this.gradleBuild.withNullMarkedEnabled(true), Task.CHECK_ARCHITECTURE_MAIN,
|
||||
"Package org.springframework.boot.build.architecture.nullmarked.notannotated is not annotated with @NullMarked");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPackageIsIgnoredAndNotAnnotatedWithNullMarkedWithMainSourcesShouldSucceedAndWriteEmptyReport()
|
||||
throws IOException {
|
||||
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "nullmarked/notannotated");
|
||||
build(this.gradleBuild.withNullMarkedEnabled(true)
|
||||
.withNullMarkedIgnoredPackages("org.springframework.boot.build.architecture.nullmarked.notannotated"),
|
||||
Task.CHECK_ARCHITECTURE_MAIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPackageIsNotAnnotatedWithNullMarkedWithTestSourcesShouldSucceedAndWriteEmptyReport() throws IOException {
|
||||
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "nullmarked/notannotated");
|
||||
build(this.gradleBuild.withNullMarked(true), Task.CHECK_ARCHITECTURE_TEST);
|
||||
build(this.gradleBuild.withNullMarkedEnabled(true), Task.CHECK_ARCHITECTURE_TEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -386,7 +398,7 @@ class ArchitectureCheckTests {
|
||||
|
||||
private final Map<Task, Boolean> prohibitObjectsRequireNonNull = new LinkedHashMap<>();
|
||||
|
||||
private Boolean nullMarked;
|
||||
private NullMarkedExtension nullMarkedExtension;
|
||||
|
||||
private GradleBuild(Path projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
@@ -396,16 +408,29 @@ class ArchitectureCheckTests {
|
||||
return this.projectDir;
|
||||
}
|
||||
|
||||
GradleBuild withNullMarked(Boolean nullMarked) {
|
||||
this.nullMarked = nullMarked;
|
||||
return this;
|
||||
}
|
||||
|
||||
GradleBuild withProhibitObjectsRequireNonNull(Task task, boolean prohibitObjectsRequireNonNull) {
|
||||
this.prohibitObjectsRequireNonNull.put(task, prohibitObjectsRequireNonNull);
|
||||
return this;
|
||||
}
|
||||
|
||||
GradleBuild withNullMarkedEnabled(Boolean enabled) {
|
||||
configureNullMarkedExtension((nullMarked) -> nullMarked.withEnabled(enabled));
|
||||
return this;
|
||||
}
|
||||
|
||||
GradleBuild withNullMarkedIgnoredPackages(String... ignorePackages) {
|
||||
configureNullMarkedExtension((nullMarked) -> nullMarked.withIgnoredPackages(ignorePackages));
|
||||
return this;
|
||||
}
|
||||
|
||||
private void configureNullMarkedExtension(UnaryOperator<NullMarkedExtension> configurer) {
|
||||
NullMarkedExtension nullMarkedExtension = this.nullMarkedExtension;
|
||||
if (nullMarkedExtension == null) {
|
||||
nullMarkedExtension = new NullMarkedExtension(null, null);
|
||||
}
|
||||
this.nullMarkedExtension = configurer.apply(nullMarkedExtension);
|
||||
}
|
||||
|
||||
GradleBuild withDependencies(String... dependencies) {
|
||||
this.dependencies.addAll(Arrays.asList(dependencies));
|
||||
return this;
|
||||
@@ -444,11 +469,22 @@ class ArchitectureCheckTests {
|
||||
.append(" prohibitObjectsRequireNonNull = ")
|
||||
.append(prohibitObjectsRequireNonNull)
|
||||
.append("\n}\n\n"));
|
||||
if (this.nullMarked != null) {
|
||||
buildFile.append("architectureCheck {\n")
|
||||
.append(" nullMarked = ")
|
||||
.append(this.nullMarked)
|
||||
.append("\n}\n");
|
||||
NullMarkedExtension nullMarkedExtension = this.nullMarkedExtension;
|
||||
if (nullMarkedExtension != null) {
|
||||
buildFile.append("architectureCheck {");
|
||||
buildFile.append("\n nullMarked {");
|
||||
if (nullMarkedExtension.enabled() != null) {
|
||||
buildFile.append("\n enabled = ").append(nullMarkedExtension.enabled());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(nullMarkedExtension.ignoredPackages())) {
|
||||
buildFile.append("\n ignoredPackages = ")
|
||||
.append(nullMarkedExtension.ignoredPackages()
|
||||
.stream()
|
||||
.map(StringUtils::quote)
|
||||
.collect(Collectors.joining(",", "[", "]")));
|
||||
}
|
||||
buildFile.append("\n }");
|
||||
buildFile.append("\n}\n\n");
|
||||
}
|
||||
Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8);
|
||||
return GradleRunner.create()
|
||||
@@ -457,6 +493,18 @@ class ArchitectureCheckTests {
|
||||
.withPluginClasspath();
|
||||
}
|
||||
|
||||
private record NullMarkedExtension(Boolean enabled, Set<String> ignoredPackages) {
|
||||
|
||||
private NullMarkedExtension withEnabled(Boolean enabled) {
|
||||
return new NullMarkedExtension(enabled, this.ignoredPackages);
|
||||
}
|
||||
|
||||
private NullMarkedExtension withIgnoredPackages(String... ignoredPackages) {
|
||||
return new NullMarkedExtension(this.enabled, new LinkedHashSet<>(Arrays.asList(ignoredPackages)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,10 @@ dependencies {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
ignoredPackages = ['org.springframework.boot.cli.json']
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("fullJar", Jar) {
|
||||
|
||||
+3
-1
@@ -39,7 +39,9 @@ dependencies {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
def dependenciesOf(String version) {
|
||||
|
||||
@@ -36,5 +36,8 @@ dependencies {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
ignoredPackages = ["org.springframework.boot.configurationmetadata.json"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,10 @@ sourceSets {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
ignoredPackages = ["org.springframework.boot.configurationprocessor.json"]
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -28,5 +28,7 @@ dependencies {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,9 @@ tasks.named('compileKotlin', KotlinCompilationTask.class) {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
plugins.withType(EclipsePlugin) {
|
||||
|
||||
@@ -38,5 +38,7 @@ tasks.configureEach {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,5 +34,7 @@ dependencies {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,5 +52,7 @@ dependencies {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,5 +32,7 @@ dependencies {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,5 +63,7 @@ dependencies {
|
||||
}
|
||||
|
||||
architectureCheck {
|
||||
nullMarked = false
|
||||
nullMarked {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user