Polish ArchitectureCheckTests

Prior to this commit, it wasn't possible to write a test to verify that
a specific ArchRule applied only to a specific SourceSet.

This PR rewrites `ArchitectureCheckTests` to support different sources
and also adds functionality to configure the plugin.

With these changes, all tests now verify not only the
`checkArchitectureMain` task but also `checkArchitectureTest`.

See gh-46822

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan 2025-08-12 23:52:08 +03:00 committed by Stéphane Nicoll
parent 39dced55a4
commit bb8e884999
1 changed files with 301 additions and 163 deletions

View File

@ -16,20 +16,32 @@
package org.springframework.boot.build.architecture;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.gradle.api.tasks.SourceSet;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.gradle.testkit.runner.UnexpectedBuildFailure;
import org.gradle.testkit.runner.UnexpectedBuildSuccess;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ClassUtils;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -43,161 +55,184 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class ArchitectureCheckTests {
private Path projectDir;
private static final String SPRING_CONTEXT = "org.springframework:spring-context:6.2.9";
private Path buildFile;
private static final String SPRING_INTEGRATION_JMX = "org.springframework.integration:spring-integration-jmx:6.5.1";
private GradleBuild gradleBuild;
@BeforeEach
void setup(@TempDir Path projectDir) {
this.projectDir = projectDir;
this.buildFile = projectDir.resolve("build.gradle");
this.gradleBuild = new GradleBuild(projectDir);
}
@Test
void whenPackagesAreTangledTaskFailsAndWritesAReport() throws IOException {
runGradleWithCompiledClasses("tangled",
shouldHaveFailureReportWithMessages("slices matching '(**)' should be free of cycles"));
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenPackagesAreTangledShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "tangled");
buildAndFail(this.gradleBuild, task, "slices matching '(**)' should be free of cycles");
}
@Test
void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws IOException {
runGradleWithCompiledClasses("untangled", shouldHaveEmptyFailureReport());
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenPackagesAreNotTangledShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
prepareTask(task, "untangled");
build(this.gradleBuild, task);
}
@Test
void whenBeanPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws IOException {
runGradleWithCompiledClasses("bpp/nonstatic",
shouldHaveFailureReportWithMessages(
"methods that are annotated with @Bean and have raw return type assignable "
+ "to org.springframework.beans.factory.config.BeanPostProcessor"));
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanPostProcessorBeanMethodIsNotStaticShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "bpp/nonstatic");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
"methods that are annotated with @Bean and have raw return type assignable"
+ " to org.springframework.beans.factory.config.BeanPostProcessor");
}
@Test
void whenBeanPostProcessorBeanMethodIsStaticAndHasUnsafeParametersTaskFailsAndWritesAReport() throws IOException {
runGradleWithCompiledClasses("bpp/unsafeparameters",
shouldHaveFailureReportWithMessages(
"methods that are annotated with @Bean and have raw return type assignable "
+ "to org.springframework.beans.factory.config.BeanPostProcessor"));
}
@Test
void whenBeanPostProcessorBeanMethodIsStaticAndHasSafeParametersTaskSucceedsAndWritesAnEmptyReport()
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanPostProcessorBeanMethodIsStaticAndHasUnsafeParametersShouldFailAndWriteReport(Task task)
throws IOException {
runGradleWithCompiledClasses("bpp/safeparameters", shouldHaveEmptyFailureReport());
prepareTask(task, "bpp/unsafeparameters");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
"methods that are annotated with @Bean and have raw return type assignable"
+ " to org.springframework.beans.factory.config.BeanPostProcessor");
}
@Test
void whenBeanPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport()
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanPostProcessorBeanMethodIsStaticAndHasSafeParametersShouldSucceedAndWriteEmptyReport(Task task)
throws IOException {
runGradleWithCompiledClasses("bpp/noparameters", shouldHaveEmptyFailureReport());
prepareTask(task, "bpp/safeparameters");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
}
@Test
void whenBeanFactoryPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws IOException {
runGradleWithCompiledClasses("bfpp/nonstatic",
shouldHaveFailureReportWithMessages("methods that are annotated with @Bean and have raw return "
+ "type assignable to org.springframework.beans.factory.config.BeanFactoryPostProcessor"));
}
@Test
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasParametersTaskFailsAndWritesAReport() throws IOException {
runGradleWithCompiledClasses("bfpp/parameters",
shouldHaveFailureReportWithMessages("methods that are annotated with @Bean and have raw return "
+ "type assignable to org.springframework.beans.factory.config.BeanFactoryPostProcessor"));
}
@Test
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport()
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanPostProcessorBeanMethodIsStaticAndHasNoParametersShouldSucceedAndWriteEmptyReport(Task task)
throws IOException {
runGradleWithCompiledClasses("bfpp/noparameters", shouldHaveEmptyFailureReport());
prepareTask(task, "bpp/noparameters");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
}
@Test
void whenClassLoadsResourceUsingResourceUtilsTaskFailsAndWritesReport() throws IOException {
runGradleWithCompiledClasses("resources/loads", shouldHaveFailureReportWithMessages(
"no classes should call method where target owner type org.springframework.util.ResourceUtils and target name 'getURL'"));
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanFactoryPostProcessorBeanMethodIsNotStaticShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "bfpp/nonstatic");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
"methods that are annotated with @Bean and have raw return type assignable"
+ " to org.springframework.beans.factory.config.BeanFactoryPostProcessor");
}
@Test
void whenClassUsesResourceUtilsWithoutLoadingResourcesTaskSucceedsAndWritesAnEmptyReport() throws IOException {
runGradleWithCompiledClasses("resources/noloads", shouldHaveEmptyFailureReport());
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasParametersShouldFailAndWriteReport(Task task)
throws IOException {
prepareTask(task, "bfpp/parameters");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
"methods that are annotated with @Bean and have raw return type assignable"
+ " to org.springframework.beans.factory.config.BeanFactoryPostProcessor");
}
@Test
void whenClassDoesNotCallObjectsRequireNonNullTaskSucceedsAndWritesAnEmptyReport() throws IOException {
runGradleWithCompiledClasses("objects/noRequireNonNull", shouldHaveEmptyFailureReport());
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersShouldSucceedAndWriteEmptyReport(Task task)
throws IOException {
prepareTask(task, "bfpp/noparameters");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
}
@Test
void whenClassCallsObjectsRequireNonNullWithMessageTaskFailsAndWritesReport() throws IOException {
runGradleWithCompiledClasses("objects/requireNonNullWithString", shouldHaveFailureReportWithMessages(
"no classes should call method Objects.requireNonNull(Object, String)"));
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassLoadsResourceUsingResourceUtilsShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "resources/loads");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), task,
"no classes should call method where target owner type"
+ " org.springframework.util.ResourceUtils and target name 'getURL'");
}
@Test
void whenClassCallsObjectsRequireNonNullWithSupplierTaskFailsAndWritesReport() throws IOException {
runGradleWithCompiledClasses("objects/requireNonNullWithSupplier", shouldHaveFailureReportWithMessages(
"no classes should call method Objects.requireNonNull(Object, Supplier)"));
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassUsesResourceUtilsWithoutLoadingResourcesShouldSucceedAndWriteEmptyReport(Task task)
throws IOException {
prepareTask(task, "resources/noloads");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
}
@Test
void whenClassCallsStringToUpperCaseWithoutLocaleFailsAndWritesReport() throws IOException {
runGradleWithCompiledClasses("string/toUpperCase",
shouldHaveFailureReportWithMessages("because String.toUpperCase(Locale.ROOT) should be used instead"));
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassDoesNotCallObjectsRequireNonNullShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
prepareTask(task, "objects/noRequireNonNull");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
}
@Test
void whenClassCallsStringToLowerCaseWithoutLocaleFailsAndWritesReport() throws IOException {
runGradleWithCompiledClasses("string/toLowerCase",
shouldHaveFailureReportWithMessages("because String.toLowerCase(Locale.ROOT) should be used instead"));
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsObjectsRequireNonNullWithMessageShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "objects/requireNonNullWithString");
buildAndFail(this.gradleBuild, task, "no classes should call method Objects.requireNonNull(Object, String)");
}
@Test
void whenClassCallsStringToLowerCaseWithLocaleShouldNotFail() throws IOException {
runGradleWithCompiledClasses("string/toLowerCaseWithLocale", shouldHaveEmptyFailureReport());
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsObjectsRequireNonNullWithMessageAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport(
Task task) throws IOException {
prepareTask(task, "objects/requireNonNullWithString");
build(this.gradleBuild.withProhibitObjectsRequireNonNull(task, false), task);
}
@Test
void whenClassCallsStringToUpperCaseWithLocaleShouldNotFail() throws IOException {
runGradleWithCompiledClasses("string/toUpperCaseWithLocale", shouldHaveEmptyFailureReport());
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsObjectsRequireNonNullWithSupplierShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "objects/requireNonNullWithSupplier");
buildAndFail(this.gradleBuild, task, "no classes should call method Objects.requireNonNull(Object, Supplier)");
}
@Test
void whenBeanMethodExposePrivateTypeShouldFailAndWriteReport() throws IOException {
runGradleWithCompiledClasses("beans/privatebean", shouldHaveFailureReportWithMessages(
"methods that are annotated with @Bean should not return types declared with the PRIVATE modifier,"
+ " as such types are incompatible with Spring AOT processing",
"Method <org.springframework.boot.build.architecture.beans.privatebean.PrivateBean.myBean()> "
+ "returns Class <org.springframework.boot.build.architecture.beans.privatebean.PrivateBean$MyBean>"
+ " which is declared as [PRIVATE, STATIC, FINAL]"));
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsObjectsRequireNonNullWithSupplierAndProhibitObjectsRequireNonNullIsFalseShouldSucceedAndWriteEmptyReport(
Task task) throws IOException {
prepareTask(task, "objects/requireNonNullWithSupplier");
build(this.gradleBuild.withProhibitObjectsRequireNonNull(task, false), task);
}
@Test
void whenBeanMethodExposeNonPrivateTypeShouldNotFail() throws IOException {
runGradleWithCompiledClasses("beans/regular", shouldHaveEmptyFailureReport());
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsStringToUpperCaseWithoutLocaleShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "string/toUpperCase");
buildAndFail(this.gradleBuild, task, "because String.toUpperCase(Locale.ROOT) should be used instead");
}
@Test
void whenBeanPostProcessorBeanMethodIsNotStaticWithExternalClass() throws IOException {
Files.writeString(this.buildFile, """
plugins {
id 'java'
id 'org.springframework.boot.architecture'
}
repositories {
mavenCentral()
}
java {
sourceCompatibility = 17
}
dependencies {
implementation("org.springframework.integration:spring-integration-jmx:6.3.9")
}
""");
Path testClass = this.projectDir.resolve("src/main/java/boot/architecture/bpp/external/TestClass.java");
Files.createDirectories(testClass.getParent());
Files.writeString(testClass, """
package org.springframework.boot.build.architecture.bpp.external;
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsStringToLowerCaseWithoutLocaleShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "string/toLowerCase");
buildAndFail(this.gradleBuild, task, "because String.toLowerCase(Locale.ROOT) should be used instead");
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsStringToLowerCaseWithLocaleShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
prepareTask(task, "string/toLowerCaseWithLocale");
build(this.gradleBuild, task);
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsStringToUpperCaseWithLocaleShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
prepareTask(task, "string/toUpperCaseWithLocale");
build(this.gradleBuild, task);
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanPostProcessorBeanMethodIsNotStaticWithExternalClassShouldFailAndWriteReport(Task task)
throws IOException {
Path sourceDirectory = task.getSourceDirectory(this.gradleBuild.getProjectDir())
.resolve(ClassUtils.classPackageAsResourcePath(getClass()));
Files.createDirectories(sourceDirectory);
Files.writeString(sourceDirectory.resolve("TestClass.java"), """
package %s;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
public class TestClass {
@ -206,68 +241,171 @@ class ArchitectureCheckTests {
return new IntegrationMBeanExporter();
}
}
""");
runGradle(shouldHaveFailureReportWithMessages("methods that are annotated with @Bean and have raw return "
+ "type assignable to org.springframework.beans.factory.config.BeanPostProcessor "));
""".formatted(ClassUtils.getPackageName(getClass())));
buildAndFail(this.gradleBuild.withDependencies(SPRING_INTEGRATION_JMX), task,
"methods that are annotated with @Bean and have raw return type assignable "
+ "to org.springframework.beans.factory.config.BeanPostProcessor");
}
private Consumer<GradleRunner> shouldHaveEmptyFailureReport() {
return (gradleRunner) -> {
try {
assertThat(gradleRunner.build().getOutput()).contains("BUILD SUCCESSFUL")
.contains("Task :checkArchitectureMain");
assertThat(failureReport()).isEmpty();
}
catch (Exception ex) {
throw new AssertionError("Expected build to succeed but it failed\n" + failureReport(), ex);
}
};
@Test
void whenBeanMethodExposesPrivateTypeWithMainSourcesShouldFailAndWriteReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "beans/privatebean");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT), Task.CHECK_ARCHITECTURE_MAIN,
"methods that are annotated with @Bean should not return types declared "
+ "with the PRIVATE modifier, as such types are incompatible with Spring AOT processing",
"returns Class <org.springframework.boot.build.architecture.beans.privatebean.PrivateBean$MyBean>"
+ " which is declared as [PRIVATE, STATIC, FINAL]");
}
private Consumer<GradleRunner> shouldHaveFailureReportWithMessages(String... messages) {
return (gradleRunner) -> {
assertThat(gradleRunner.buildAndFail().getOutput()).contains("BUILD FAILED")
.contains("Task :checkArchitectureMain FAILED");
assertThat(failureReport()).contains(messages);
};
@Test
void whenBeanMethodExposesPrivateTypeWithTestsSourcesShouldSucceedAndWriteEmptyReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "beans/privatebean");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), Task.CHECK_ARCHITECTURE_TEST);
}
private void runGradleWithCompiledClasses(String path, Consumer<GradleRunner> callback) throws IOException {
ClassPathResource classPathResource = new ClassPathResource(path, getClass());
FileSystemUtils.copyRecursively(classPathResource.getFile().toPath(),
this.projectDir.resolve("classes").resolve(classPathResource.getPath()));
Files.writeString(this.buildFile, """
plugins {
id 'java'
id 'org.springframework.boot.architecture'
}
sourceSets {
main {
output.classesDirs.setFrom(file("classes"))
}
}
""");
runGradle(callback);
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanMethodExposesNonPrivateTypeShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
prepareTask(task, "beans/regular");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
}
private void runGradle(Consumer<GradleRunner> callback) {
callback.accept(GradleRunner.create()
.withProjectDir(this.projectDir.toFile())
.withArguments("checkArchitectureMain")
.withPluginClasspath());
private void prepareTask(Task task, String... sourceDirectories) throws IOException {
for (String sourceDirectory : sourceDirectories) {
FileSystemUtils.copyRecursively(
Paths.get("src/test/java")
.resolve(ClassUtils.classPackageAsResourcePath(getClass()))
.resolve(sourceDirectory),
task.getSourceDirectory(this.gradleBuild.getProjectDir())
.resolve(ClassUtils.classPackageAsResourcePath(getClass()))
.resolve(sourceDirectory));
}
}
private String failureReport() {
private void build(GradleBuild gradleBuild, Task task) throws IOException {
try {
Path failureReport = this.projectDir.resolve("build/checkArchitectureMain/failure-report.txt");
return Files.readString(failureReport, StandardCharsets.UTF_8);
BuildResult buildResult = gradleBuild.build(task.toString());
assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).contains(":" + task);
assertThat(task.getFailureReport(gradleBuild.getProjectDir())).isEmpty();
}
catch (FileNotFoundException ex) {
return "Failure report does not exist";
catch (UnexpectedBuildFailure ex) {
StringBuilder message = new StringBuilder("Expected build to succeed but it failed");
if (Files.exists(task.getFailureReportFile(gradleBuild.getProjectDir()))) {
message.append('\n').append(task.getFailureReport(gradleBuild.getProjectDir()));
}
message.append('\n').append(ex.getBuildResult().getOutput());
throw new AssertionError(message.toString(), ex);
}
catch (IOException ex) {
return "Failure report could not be read: " + ex.getMessage();
}
private void buildAndFail(GradleBuild gradleBuild, Task task, String... messages) throws IOException {
try {
BuildResult buildResult = gradleBuild.buildAndFail(task.toString());
assertThat(buildResult.taskPaths(TaskOutcome.FAILED)).contains(":" + task);
assertThat(task.getFailureReport(gradleBuild.getProjectDir())).contains(messages);
}
catch (UnexpectedBuildSuccess ex) {
throw new AssertionError("Expected build to fail but it succeeded\n" + ex.getBuildResult().getOutput(), ex);
}
}
private enum Task {
CHECK_ARCHITECTURE_MAIN(SourceSet.MAIN_SOURCE_SET_NAME),
CHECK_ARCHITECTURE_TEST(SourceSet.TEST_SOURCE_SET_NAME);
private final String sourceSetName;
Task(String sourceSetName) {
this.sourceSetName = sourceSetName;
}
String getFailureReport(Path projectDir) throws IOException {
return Files.readString(getFailureReportFile(projectDir), StandardCharsets.UTF_8);
}
Path getFailureReportFile(Path projectDir) {
return projectDir.resolve("build/%s/failure-report.txt".formatted(toString()));
}
Path getSourceDirectory(Path projectDir) {
return projectDir.resolve("src/%s/java".formatted(this.sourceSetName));
}
@Override
public String toString() {
return "checkArchitecture" + StringUtils.capitalize(this.sourceSetName);
}
}
private static final class GradleBuild {
private final Path projectDir;
private final Set<String> dependencies = new LinkedHashSet<>();
private final Map<Task, Boolean> prohibitObjectsRequireNonNull = new LinkedHashMap<>();
private GradleBuild(Path projectDir) {
this.projectDir = projectDir;
}
Path getProjectDir() {
return this.projectDir;
}
GradleBuild withProhibitObjectsRequireNonNull(Task task, boolean prohibitObjectsRequireNonNull) {
this.prohibitObjectsRequireNonNull.put(task, prohibitObjectsRequireNonNull);
return this;
}
GradleBuild withDependencies(String... dependencies) {
this.dependencies.addAll(Arrays.asList(dependencies));
return this;
}
BuildResult build(String... arguments) throws IOException {
return prepareRunner(arguments).build();
}
BuildResult buildAndFail(String... arguments) throws IOException {
return prepareRunner(arguments).buildAndFail();
}
private GradleRunner prepareRunner(String... arguments) throws IOException {
StringBuilder buildFile = new StringBuilder();
buildFile.append("plugins {\n")
.append(" id 'java'\n")
.append(" id 'org.springframework.boot.architecture'\n")
.append("}\n\n")
.append("repositories {\n")
.append(" mavenCentral()\n")
.append("}\n\n")
.append("java {\n")
.append(" sourceCompatibility = '17'\n")
.append(" targetCompatibility = '17'\n")
.append("}\n\n");
if (!this.dependencies.isEmpty()) {
buildFile.append("dependencies {\n");
for (String dependency : this.dependencies) {
buildFile.append(" implementation '%s'\n".formatted(dependency));
}
buildFile.append("}\n");
}
this.prohibitObjectsRequireNonNull.forEach((task, prohibitObjectsRequireNonNull) -> buildFile.append(task)
.append(" {\n")
.append(" prohibitObjectsRequireNonNull = ")
.append(prohibitObjectsRequireNonNull)
.append("\n}\n\n"));
Files.writeString(this.projectDir.resolve("build.gradle"), buildFile, StandardCharsets.UTF_8);
return GradleRunner.create()
.withProjectDir(this.projectDir.toFile())
.withArguments(arguments)
.withPluginClasspath();
}
}
}