Browse Source

Merge pull request #47596 from nosan

* pr/47596:
  Extend ArchitectureCheck with NullMarkedExtension

Closes gh-47596
pull/47637/head
Stéphane Nicoll 2 months ago
parent
commit
b5a8d605d7
  1. 10
      buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java
  2. 55
      buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckExtension.java
  3. 3
      buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java
  4. 9
      buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java
  5. 74
      buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java
  6. 5
      cli/spring-boot-cli/build.gradle
  7. 4
      configuration-metadata/spring-boot-configuration-metadata-changelog-generator/build.gradle
  8. 5
      configuration-metadata/spring-boot-configuration-metadata/build.gradle
  9. 5
      configuration-metadata/spring-boot-configuration-processor/build.gradle
  10. 4
      core/spring-boot-autoconfigure-processor/build.gradle
  11. 4
      documentation/spring-boot-docs/build.gradle
  12. 4
      loader/spring-boot-loader/build.gradle
  13. 4
      smoke-test/spring-boot-smoke-test-webflux-coroutines/build.gradle
  14. 4
      test-support/spring-boot-docker-test-support/build.gradle
  15. 4
      test-support/spring-boot-gradle-test-support/build.gradle
  16. 4
      test-support/spring-boot-test-support/build.gradle

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

@ -44,6 +44,7 @@ import org.gradle.api.file.FileTree; @@ -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 { @@ -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 { @@ -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();
}

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

@ -16,8 +16,14 @@ @@ -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; @@ -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);
}
/**
* 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();
}
}

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

@ -59,7 +59,8 @@ public class ArchitecturePlugin implements Plugin<Project> { @@ -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);
}

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

@ -82,11 +82,6 @@ final class ArchitectureRules { @@ -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 { @@ -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);
}

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

@ -26,6 +26,8 @@ import java.util.LinkedHashMap; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -396,16 +408,29 @@ class ArchitectureCheckTests {
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;
@ -444,11 +469,22 @@ class ArchitectureCheckTests { @@ -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 { @@ -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)));
}
}
}
}

5
cli/spring-boot-cli/build.gradle

@ -62,7 +62,10 @@ dependencies { @@ -62,7 +62,10 @@ dependencies {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
ignoredPackages = ['org.springframework.boot.cli.json']
}
}
tasks.register("fullJar", Jar) {

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

@ -39,7 +39,9 @@ dependencies { @@ -39,7 +39,9 @@ dependencies {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}
def dependenciesOf(String version) {

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

@ -36,5 +36,8 @@ dependencies { @@ -36,5 +36,8 @@ dependencies {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
ignoredPackages = ["org.springframework.boot.configurationmetadata.json"]
}
}

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

@ -31,7 +31,10 @@ sourceSets { @@ -31,7 +31,10 @@ sourceSets {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
ignoredPackages = ["org.springframework.boot.configurationprocessor.json"]
}
}
dependencies {

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

@ -28,5 +28,7 @@ dependencies { @@ -28,5 +28,7 @@ dependencies {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}

4
documentation/spring-boot-docs/build.gradle

@ -71,7 +71,9 @@ tasks.named('compileKotlin', KotlinCompilationTask.class) { @@ -71,7 +71,9 @@ tasks.named('compileKotlin', KotlinCompilationTask.class) {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}
plugins.withType(EclipsePlugin) {

4
loader/spring-boot-loader/build.gradle

@ -38,5 +38,7 @@ tasks.configureEach { @@ -38,5 +38,7 @@ tasks.configureEach {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}

4
smoke-test/spring-boot-smoke-test-webflux-coroutines/build.gradle

@ -34,5 +34,7 @@ dependencies { @@ -34,5 +34,7 @@ dependencies {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}

4
test-support/spring-boot-docker-test-support/build.gradle

@ -52,5 +52,7 @@ dependencies { @@ -52,5 +52,7 @@ dependencies {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}

4
test-support/spring-boot-gradle-test-support/build.gradle

@ -32,5 +32,7 @@ dependencies { @@ -32,5 +32,7 @@ dependencies {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}

4
test-support/spring-boot-test-support/build.gradle

@ -63,5 +63,7 @@ dependencies { @@ -63,5 +63,7 @@ dependencies {
}
architectureCheck {
nullMarked = false
nullMarked {
enabled = false
}
}

Loading…
Cancel
Save