Skip to content

Commit 878baae

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

File tree

16 files changed

+157
-40
lines changed

16 files changed

+157
-40
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: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.util.LinkedHashSet;
2727
import java.util.Map;
2828
import java.util.Set;
29+
import java.util.function.UnaryOperator;
30+
import java.util.stream.Collectors;
2931

3032
import org.gradle.api.tasks.SourceSet;
3133
import org.gradle.testkit.runner.BuildResult;
@@ -40,6 +42,7 @@
4042
import org.junit.jupiter.params.provider.EnumSource;
4143

4244
import org.springframework.util.ClassUtils;
45+
import org.springframework.util.CollectionUtils;
4346
import org.springframework.util.FileSystemUtils;
4447
import org.springframework.util.StringUtils;
4548

@@ -65,7 +68,7 @@ class ArchitectureCheckTests {
6568

6669
@BeforeEach
6770
void setup(@TempDir Path projectDir) {
68-
this.gradleBuild = new GradleBuild(projectDir).withNullMarked(false);
71+
this.gradleBuild = new GradleBuild(projectDir).withNullMarkedEnabled(false);
6972
}
7073

7174
@ParameterizedTest(name = "{0}")
@@ -275,14 +278,23 @@ void whenBeanMethodExposesNonPrivateTypeShouldSucceedAndWriteEmptyReport(Task ta
275278
@Test
276279
void whenPackageIsNotAnnotatedWithNullMarkedWithMainSourcesShouldFailAndWriteEmptyReport() throws IOException {
277280
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "nullmarked/notannotated");
278-
buildAndFail(this.gradleBuild.withNullMarked(true), Task.CHECK_ARCHITECTURE_MAIN,
281+
buildAndFail(this.gradleBuild.withNullMarkedEnabled(true), Task.CHECK_ARCHITECTURE_MAIN,
279282
"Package org.springframework.boot.build.architecture.nullmarked.notannotated is not annotated with @NullMarked");
280283
}
281284

285+
@Test
286+
void whenPackageIsIgnoredAndNotAnnotatedWithNullMarkedWithMainSourcesShouldSucceedAndWriteEmptyReport()
287+
throws IOException {
288+
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "nullmarked/notannotated");
289+
build(this.gradleBuild.withNullMarkedEnabled(true)
290+
.withNullMarkedIgnoredPackages("org.springframework.boot.build.architecture.nullmarked.notannotated"),
291+
Task.CHECK_ARCHITECTURE_MAIN);
292+
}
293+
282294
@Test
283295
void whenPackageIsNotAnnotatedWithNullMarkedWithTestSourcesShouldSucceedAndWriteEmptyReport() throws IOException {
284296
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "nullmarked/notannotated");
285-
build(this.gradleBuild.withNullMarked(true), Task.CHECK_ARCHITECTURE_TEST);
297+
build(this.gradleBuild.withNullMarkedEnabled(true), Task.CHECK_ARCHITECTURE_TEST);
286298
}
287299

288300
@Test
@@ -386,7 +398,7 @@ private static final class GradleBuild {
386398

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

389-
private Boolean nullMarked;
401+
private NullMarkedExtension nullMarkedExtension;
390402

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

399-
GradleBuild withNullMarked(Boolean nullMarked) {
400-
this.nullMarked = nullMarked;
411+
GradleBuild withProhibitObjectsRequireNonNull(Task task, boolean prohibitObjectsRequireNonNull) {
412+
this.prohibitObjectsRequireNonNull.put(task, prohibitObjectsRequireNonNull);
413+
return this;
414+
}
415+
416+
GradleBuild withNullMarkedEnabled(Boolean enabled) {
417+
configureNullExtension((nullMarked) -> nullMarked.withEnabled(enabled));
401418
return this;
402419
}
403420

404-
GradleBuild withProhibitObjectsRequireNonNull(Task task, boolean prohibitObjectsRequireNonNull) {
405-
this.prohibitObjectsRequireNonNull.put(task, prohibitObjectsRequireNonNull);
421+
GradleBuild withNullMarkedIgnoredPackages(String... ignorePackages) {
422+
configureNullExtension((nullMarked) -> nullMarked.withIgnoredPackages(ignorePackages));
406423
return this;
407424
}
408425

426+
private void configureNullExtension(UnaryOperator<NullMarkedExtension> configurer) {
427+
NullMarkedExtension nullMarkedExtension = this.nullMarkedExtension;
428+
if (nullMarkedExtension == null) {
429+
nullMarkedExtension = new NullMarkedExtension(null, null);
430+
}
431+
this.nullMarkedExtension = configurer.apply(nullMarkedExtension);
432+
}
433+
409434
GradleBuild withDependencies(String... dependencies) {
410435
this.dependencies.addAll(Arrays.asList(dependencies));
411436
return this;
@@ -444,11 +469,22 @@ private GradleRunner prepareRunner(String... arguments) throws IOException {
444469
.append(" prohibitObjectsRequireNonNull = ")
445470
.append(prohibitObjectsRequireNonNull)
446471
.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");
472+
NullMarkedExtension nullMarkedExtension = this.nullMarkedExtension;
473+
if (nullMarkedExtension != null) {
474+
buildFile.append("architectureCheck {");
475+
buildFile.append("\n nullMarked {");
476+
if (nullMarkedExtension.enabled() != null) {
477+
buildFile.append("\n enabled = ").append(nullMarkedExtension.enabled());
478+
}
479+
if (!CollectionUtils.isEmpty(nullMarkedExtension.ignoredPackages())) {
480+
buildFile.append("\n ignoredPackages = ")
481+
.append(nullMarkedExtension.ignoredPackages()
482+
.stream()
483+
.map(StringUtils::quote)
484+
.collect(Collectors.joining(",", "[", "]")));
485+
}
486+
buildFile.append("\n }");
487+
buildFile.append("\n}\n\n");
452488
}
453489
Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8);
454490
return GradleRunner.create()
@@ -457,6 +493,18 @@ private GradleRunner prepareRunner(String... arguments) throws IOException {
457493
.withPluginClasspath();
458494
}
459495

496+
private record NullMarkedExtension(Boolean enabled, Set<String> ignoredPackages) {
497+
498+
private NullMarkedExtension withEnabled(Boolean enabled) {
499+
return new NullMarkedExtension(enabled, this.ignoredPackages);
500+
}
501+
502+
private NullMarkedExtension withIgnoredPackages(String... ignoredPackages) {
503+
return new NullMarkedExtension(this.enabled, new LinkedHashSet<>(Arrays.asList(ignoredPackages)));
504+
}
505+
506+
}
507+
460508
}
461509

462510
}

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)