Skip to content

Commit c20bef6

Browse files
committed
Extend NullMarked extension with ignoredPackages
Signed-off-by: Dmytro Nosan <[email protected]>
1 parent 52eea3d commit c20bef6

File tree

16 files changed

+135
-39
lines changed

16 files changed

+135
-39
lines changed

buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.gradle.api.provider.ListProperty;
4545
import org.gradle.api.provider.Property;
4646
import org.gradle.api.provider.Provider;
47+
import org.gradle.api.provider.SetProperty;
4748
import org.gradle.api.tasks.Classpath;
4849
import org.gradle.api.tasks.IgnoreEmptyDirectories;
4950
import org.gradle.api.tasks.Input;
@@ -80,8 +81,8 @@ public ArchitectureCheck() {
8081
getRules().addAll(ArchitectureRules.standard());
8182
getRules().addAll(whenMainSources(
8283
() -> Collections.singletonList(ArchitectureRules.allBeanMethodsShouldReturnNonPrivateType())));
83-
getRules().addAll(and(getNullMarked(), isMainSourceSet()).map(whenTrue(
84-
() -> Collections.singletonList(ArchitectureRules.packagesShouldBeAnnotatedWithNullMarked()))));
84+
getRules().addAll(and(getNullMarkedEnabled(), isMainSourceSet()).map(whenTrue(() -> Collections.singletonList(
85+
ArchitectureRules.packagesShouldBeAnnotatedWithNullMarked(getNullMarkedIgnoredPackages().get())))));
8586
getRuleDescriptions().set(getRules().map(this::asDescriptions));
8687
}
8788

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

198199
@Internal
199-
abstract Property<Boolean> getNullMarked();
200+
abstract Property<Boolean> getNullMarkedEnabled();
201+
202+
@Internal
203+
abstract SetProperty<String> getNullMarkedIgnoredPackages();
200204

201205
}

buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckExtension.java

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@
1616

1717
package org.springframework.boot.build.architecture;
1818

19+
import java.util.LinkedHashSet;
20+
21+
import javax.inject.Inject;
22+
23+
import org.gradle.api.Action;
24+
import org.gradle.api.model.ObjectFactory;
1925
import org.gradle.api.provider.Property;
26+
import org.gradle.api.provider.SetProperty;
2027
import org.jspecify.annotations.NullMarked;
2128

2229
/**
@@ -26,14 +33,51 @@
2633
*/
2734
public abstract class ArchitectureCheckExtension {
2835

29-
public ArchitectureCheckExtension() {
30-
getNullMarked().convention(true);
36+
private final NullMarkedExtension nullMarked;
37+
38+
@Inject
39+
public ArchitectureCheckExtension(ObjectFactory objects) {
40+
this.nullMarked = objects.newInstance(NullMarkedExtension.class);
41+
}
42+
43+
/**
44+
* Configure the {@link NullMarked} extension.
45+
* @return the {@link NullMarked} extension
46+
*/
47+
public NullMarkedExtension getNullMarked() {
48+
return this.nullMarked;
49+
}
50+
51+
/**
52+
* Configure the {@link NullMarked} extension.
53+
* @param action the action to configure the {@link NullMarked} extension with
54+
*/
55+
public void nullMarked(Action<? super NullMarkedExtension> action) {
56+
action.execute(this.nullMarked);
3157
}
3258

3359
/**
34-
* Whether this project uses JSpecify's {@link NullMarked} annotations.
35-
* @return whether this project uses JSpecify's @NullMarked annotations
60+
* Extension to configure the {@link NullMarked} extension.
3661
*/
37-
public abstract Property<Boolean> getNullMarked();
62+
public abstract static class NullMarkedExtension {
63+
64+
public NullMarkedExtension() {
65+
getEnabled().convention(true);
66+
getIgnoredPackages().convention(new LinkedHashSet<>());
67+
}
68+
69+
/**
70+
* Whether this project uses JSpecify's {@link NullMarked} annotations.
71+
* @return whether this project uses JSpecify's @NullMarked annotations
72+
*/
73+
public abstract Property<Boolean> getEnabled();
74+
75+
/**
76+
* Packages that should be ignored by the {@code NullMarked} checker.
77+
* @return the ignored packages
78+
*/
79+
public abstract SetProperty<String> getIgnoredPackages();
80+
81+
}
3882

3983
}

buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ private void registerTasks(Project project, ArchitectureCheckExtension extension
5959
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
6060
+ " source set.");
6161
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
62-
task.getNullMarked().set(extension.getNullMarked());
62+
task.getNullMarkedEnabled().set(extension.getNullMarked().getEnabled());
63+
task.getNullMarkedIgnoredPackages().set(extension.getNullMarked().getIgnoredPackages());
6364
});
6465
packageTangleChecks.add(checkPackageTangles);
6566
}

buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,6 @@ final class ArchitectureRules {
8282

8383
private static final String TEST_AUTOCONFIGURATION_ANNOTATION = "org.springframework.boot.test.autoconfigure.TestAutoConfiguration";
8484

85-
private static final Predicate<JavaPackage> NULL_MARKED_PACKAGE_FILTER = (candidate) -> !List
86-
.of("org.springframework.boot.cli.json", "org.springframework.boot.configurationmetadata.json",
87-
"org.springframework.boot.configurationprocessor.json")
88-
.contains(candidate.getName());
89-
9085
private ArchitectureRules() {
9186
}
9287

@@ -262,8 +257,8 @@ private static ArchRule conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsT
262257
.allowEmptyShould(true);
263258
}
264259

265-
static ArchRule packagesShouldBeAnnotatedWithNullMarked() {
266-
return ArchRuleDefinition.all(packages(NULL_MARKED_PACKAGE_FILTER))
260+
static ArchRule packagesShouldBeAnnotatedWithNullMarked(Set<String> ignoredPackages) {
261+
return ArchRuleDefinition.all(packages((javaPackage) -> !ignoredPackages.contains(javaPackage.getName())))
267262
.should(beAnnotatedWithNullMarked())
268263
.allowEmptyShould(true);
269264
}

buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.LinkedHashSet;
2727
import java.util.Map;
2828
import java.util.Set;
29+
import java.util.stream.Collectors;
2930

3031
import org.gradle.api.tasks.SourceSet;
3132
import org.gradle.testkit.runner.BuildResult;
@@ -65,7 +66,7 @@ class ArchitectureCheckTests {
6566

6667
@BeforeEach
6768
void setup(@TempDir Path projectDir) {
68-
this.gradleBuild = new GradleBuild(projectDir).withNullMarked(false);
69+
this.gradleBuild = new GradleBuild(projectDir).withNullMarkedEnabled(false);
6970
}
7071

7172
@ParameterizedTest(name = "{0}")
@@ -275,14 +276,23 @@ void whenBeanMethodExposesNonPrivateTypeShouldSucceedAndWriteEmptyReport(Task ta
275276
@Test
276277
void whenPackageIsNotAnnotatedWithNullMarkedWithMainSourcesShouldFailAndWriteEmptyReport() throws IOException {
277278
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "nullmarked/notannotated");
278-
buildAndFail(this.gradleBuild.withNullMarked(true), Task.CHECK_ARCHITECTURE_MAIN,
279+
buildAndFail(this.gradleBuild.withNullMarkedEnabled(true), Task.CHECK_ARCHITECTURE_MAIN,
279280
"Package org.springframework.boot.build.architecture.nullmarked.notannotated is not annotated with @NullMarked");
280281
}
281282

283+
@Test
284+
void whenPackageIsIgnoredAndNotAnnotatedWithNullMarkedWithMainSourcesShouldSucceedAndWriteEmptyReport()
285+
throws IOException {
286+
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "nullmarked/notannotated");
287+
build(this.gradleBuild.withNullMarkedEnabled(true)
288+
.withNullMarkedIgnoredPackages("org.springframework.boot.build.architecture.nullmarked.notannotated"),
289+
Task.CHECK_ARCHITECTURE_MAIN);
290+
}
291+
282292
@Test
283293
void whenPackageIsNotAnnotatedWithNullMarkedWithTestSourcesShouldSucceedAndWriteEmptyReport() throws IOException {
284294
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "nullmarked/notannotated");
285-
build(this.gradleBuild.withNullMarked(true), Task.CHECK_ARCHITECTURE_TEST);
295+
build(this.gradleBuild.withNullMarkedEnabled(true), Task.CHECK_ARCHITECTURE_TEST);
286296
}
287297

288298
@Test
@@ -384,9 +394,11 @@ private static final class GradleBuild {
384394

385395
private final Set<String> dependencies = new LinkedHashSet<>();
386396

397+
private final Set<String> nullMarkedIgnoredPackages = new LinkedHashSet<>();
398+
387399
private final Map<Task, Boolean> prohibitObjectsRequireNonNull = new LinkedHashMap<>();
388400

389-
private Boolean nullMarked;
401+
private Boolean nullMarkedEnabled;
390402

391403
private GradleBuild(Path projectDir) {
392404
this.projectDir = projectDir;
@@ -396,8 +408,14 @@ Path getProjectDir() {
396408
return this.projectDir;
397409
}
398410

399-
GradleBuild withNullMarked(Boolean nullMarked) {
400-
this.nullMarked = nullMarked;
411+
GradleBuild withNullMarkedEnabled(Boolean nullMarked) {
412+
this.nullMarkedEnabled = nullMarked;
413+
return this;
414+
}
415+
416+
GradleBuild withNullMarkedIgnoredPackages(String... ignorePackages) {
417+
this.nullMarkedIgnoredPackages.clear();
418+
this.nullMarkedIgnoredPackages.addAll(Arrays.asList(ignorePackages));
401419
return this;
402420
}
403421

@@ -437,19 +455,28 @@ private GradleRunner prepareRunner(String... arguments) throws IOException {
437455
for (String dependency : this.dependencies) {
438456
buildFile.append(" implementation '%s'\n".formatted(dependency));
439457
}
440-
buildFile.append("}\n");
458+
buildFile.append("}\n\n");
441459
}
442460
this.prohibitObjectsRequireNonNull.forEach((task, prohibitObjectsRequireNonNull) -> buildFile.append(task)
443461
.append(" {\n")
444462
.append(" prohibitObjectsRequireNonNull = ")
445463
.append(prohibitObjectsRequireNonNull)
446464
.append("\n}\n\n"));
447-
if (this.nullMarked != null) {
448-
buildFile.append("architectureCheck {\n")
449-
.append(" nullMarked = ")
450-
.append(this.nullMarked)
451-
.append("\n}\n");
465+
if (this.nullMarkedEnabled != null || !this.nullMarkedIgnoredPackages.isEmpty()) {
466+
buildFile.append("architectureCheck {");
467+
buildFile.append("\n nullMarked {");
468+
if (this.nullMarkedEnabled != null) {
469+
buildFile.append("\n enabled = ").append(this.nullMarkedEnabled);
470+
}
471+
if (!this.nullMarkedIgnoredPackages.isEmpty()) {
472+
buildFile.append("\n ignoredPackages = ")
473+
.append(this.nullMarkedIgnoredPackages.stream()
474+
.map(StringUtils::quote)
475+
.collect(Collectors.joining(",", "[", "]")));
476+
buildFile.append("\n }");
477+
}
452478
}
479+
buildFile.append("\n}\n\n");
453480
Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8);
454481
return GradleRunner.create()
455482
.withProjectDir(this.projectDir.toFile())

cli/spring-boot-cli/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ dependencies {
6262
}
6363

6464
architectureCheck {
65-
nullMarked = false
65+
nullMarked {
66+
enabled = false
67+
ignoredPackages = ['org.springframework.boot.cli.json']
68+
}
6669
}
6770

6871
tasks.register("fullJar", Jar) {

configuration-metadata/spring-boot-configuration-metadata-changelog-generator/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ dependencies {
3939
}
4040

4141
architectureCheck {
42-
nullMarked = false
42+
nullMarked {
43+
enabled = false
44+
}
4345
}
4446

4547
def dependenciesOf(String version) {

configuration-metadata/spring-boot-configuration-metadata/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,8 @@ dependencies {
3636
}
3737

3838
architectureCheck {
39-
nullMarked = false
39+
nullMarked {
40+
enabled = false
41+
ignoredPackages = ["org.springframework.boot.configurationmetadata.json"]
42+
}
4043
}

configuration-metadata/spring-boot-configuration-processor/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ sourceSets {
3131
}
3232

3333
architectureCheck {
34-
nullMarked = false
34+
nullMarked {
35+
enabled = false
36+
ignoredPackages = ["org.springframework.boot.configurationprocessor.json"]
37+
}
3538
}
3639

3740
dependencies {

core/spring-boot-autoconfigure-processor/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@ dependencies {
2828
}
2929

3030
architectureCheck {
31-
nullMarked = false
31+
nullMarked {
32+
enabled = false
33+
}
3234
}

0 commit comments

Comments
 (0)