Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
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;
Expand Down Expand Up @@ -80,8 +81,8 @@ public ArchitectureCheck() {
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));
}

Expand Down Expand Up @@ -196,6 +197,9 @@ final FileTree getInputClasses() {
abstract ListProperty<String> getRuleDescriptions();

@Internal
abstract Property<Boolean> getNullMarked();
abstract Property<Boolean> getNullMarkedEnabled();

@Internal
abstract SetProperty<String> getNullMarkedIgnoredPackages();

}
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand All @@ -26,14 +32,51 @@
*/
public abstract class ArchitectureCheckExtension {

public ArchitectureCheckExtension() {
getNullMarked().convention(true);
private final NullMarkedExtension nullMarked;

@Inject
public ArchitectureCheckExtension(ObjectFactory objects) {
this.nullMarked = objects.newInstance(NullMarkedExtension.class);
}

/**
* Get the {@code NullMarked} extension.
* @return the {@code NullMarked} extension
*/
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);
}

/**
* Whether this project uses JSpecify's {@link NullMarked} annotations.
* @return whether this project uses JSpecify's @NullMarked annotations
* Extension to configure the {@code NullMarked} extension.
*/
public abstract Property<Boolean> getNullMarked();
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();

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ private void registerTasks(Project project, ArchitectureCheckExtension extension
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}

Expand Down Expand Up @@ -262,8 +257,8 @@ private static ArchRule conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsT
.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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
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;
Expand All @@ -40,6 +42,7 @@
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;

Expand All @@ -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}")
Expand Down Expand Up @@ -275,14 +278,23 @@ void whenBeanMethodExposesNonPrivateTypeShouldSucceedAndWriteEmptyReport(Task ta
@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
Expand Down Expand Up @@ -386,7 +398,7 @@ private static final class GradleBuild {

private final Map<Task, Boolean> prohibitObjectsRequireNonNull = new LinkedHashMap<>();

private Boolean nullMarked;
private NullMarkedExtension nullMarkedExtension;

private GradleBuild(Path projectDir) {
this.projectDir = projectDir;
Expand All @@ -396,16 +408,29 @@ Path getProjectDir() {
return this.projectDir;
}

GradleBuild withNullMarked(Boolean nullMarked) {
this.nullMarked = nullMarked;
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 withProhibitObjectsRequireNonNull(Task task, boolean prohibitObjectsRequireNonNull) {
this.prohibitObjectsRequireNonNull.put(task, prohibitObjectsRequireNonNull);
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;
Expand Down Expand Up @@ -444,11 +469,22 @@ private GradleRunner prepareRunner(String... arguments) throws IOException {
.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()
Expand All @@ -457,6 +493,18 @@ private GradleRunner prepareRunner(String... arguments) throws IOException {
.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)));
}

}

}

}
5 changes: 4 additions & 1 deletion cli/spring-boot-cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ dependencies {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
ignoredPackages = ['org.springframework.boot.cli.json']
}
}

tasks.register("fullJar", Jar) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ dependencies {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}

def dependenciesOf(String version) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@ dependencies {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
ignoredPackages = ["org.springframework.boot.configurationmetadata.json"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ sourceSets {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
ignoredPackages = ["org.springframework.boot.configurationprocessor.json"]
}
}

dependencies {
Expand Down
4 changes: 3 additions & 1 deletion core/spring-boot-autoconfigure-processor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ dependencies {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}
4 changes: 3 additions & 1 deletion documentation/spring-boot-docs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ tasks.named('compileKotlin', KotlinCompilationTask.class) {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}

plugins.withType(EclipsePlugin) {
Expand Down
4 changes: 3 additions & 1 deletion loader/spring-boot-loader/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ tasks.configureEach {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ dependencies {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}
4 changes: 3 additions & 1 deletion test-support/spring-boot-docker-test-support/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,7 @@ dependencies {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}
4 changes: 3 additions & 1 deletion test-support/spring-boot-gradle-test-support/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ dependencies {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}
4 changes: 3 additions & 1 deletion test-support/spring-boot-test-support/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,7 @@ dependencies {
}

architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}