From 5bac62606f4f3e828219ffd4f04bcbaddaecf172 Mon Sep 17 00:00:00 2001 From: Linda Navarette Date: Mon, 6 Dec 2021 01:35:43 -0500 Subject: [PATCH] support for multi module gradle builds --- .../gradle/GradleBuildProjectContributor.java | 6 +- .../GradleProjectGenerationConfiguration.java | 21 +++++- ...urationProjectGenerationConfiguration.java | 16 +++- .../ApplicationPropertiesContributor.java | 14 +++- .../configuration/ResourcesPathProvider.java | 35 +++++++++ .../configuration/WebFoldersContributor.java | 13 +++- .../GradleBuildProjectContributorTests.java | 10 +++ ...leProjectGenerationConfigurationTests.java | 17 +++++ ...onProjectGenerationConfigurationTests.java | 74 +++++++++++++++++++ ...ApplicationPropertiesContributorTests.java | 7 ++ .../WebFoldersContributorTests.java | 3 +- .../generator/buildsystem/BuildSettings.java | 26 +++++++ .../generator/buildsystem/BuildSystem.java | 31 +++++++- .../buildsystem/gradle/GradleBuildSystem.java | 12 +++ .../gradle/GradleSettingsWriter.java | 1 + .../condition/ConditionalOnBuildSystem.java | 8 ++ .../condition/OnBuildSystemCondition.java | 15 ++-- .../SingleResourceProjectContributor.java | 11 ++- .../buildsystem/BuildSystemTests.java | 27 +++++++ .../GroovyDslGradleSettingsWriterTests.java | 8 ++ .../KotlinDslGradleSettingsWriterTests.java | 8 ++ .../ConditionalOnBuildSystemTests.java | 14 ++++ 22 files changed, 351 insertions(+), 26 deletions(-) create mode 100644 initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ResourcesPathProvider.java create mode 100644 initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/ApplicationConfigurationProjectGenerationConfigurationTests.java diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/build/gradle/GradleBuildProjectContributor.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/build/gradle/GradleBuildProjectContributor.java index 7f1bd62742..57fa34f2cf 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/build/gradle/GradleBuildProjectContributor.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/build/gradle/GradleBuildProjectContributor.java @@ -54,7 +54,11 @@ public class GradleBuildProjectContributor implements BuildWriter, ProjectContri @Override public void contribute(Path projectRoot) throws IOException { - Path buildGradle = Files.createFile(projectRoot.resolve(this.buildFileName)); + Path buildGradle = projectRoot.resolve(this.buildFileName); + if (!Files.exists(buildGradle)) { + Files.createDirectories(buildGradle.getParent()); + Files.createFile(buildGradle); + } writeBuild(Files.newBufferedWriter(buildGradle)); } diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/build/gradle/GradleProjectGenerationConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/build/gradle/GradleProjectGenerationConfiguration.java index 846d5d5da6..61bf37c6a7 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/build/gradle/GradleProjectGenerationConfiguration.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/build/gradle/GradleProjectGenerationConfiguration.java @@ -111,15 +111,28 @@ BuildCustomizer springBootPluginContributor(ProjectDescription desc @Bean @ConditionalOnBuildSystem(id = GradleBuildSystem.ID, dialect = GradleBuildSystem.DIALECT_GROOVY) public GradleBuildProjectContributor gradleBuildProjectContributor(GroovyDslGradleBuildWriter buildWriter, - GradleBuild build) { - return new GradleBuildProjectContributor(buildWriter, build, this.indentingWriterFactory, "build.gradle"); + GradleBuild build, ProjectDescription description) { + String buildFileName = gradleBuildFilePath(description, "build.gradle"); + return new GradleBuildProjectContributor(buildWriter, build, this.indentingWriterFactory, buildFileName); } @Bean @ConditionalOnBuildSystem(id = GradleBuildSystem.ID, dialect = GradleBuildSystem.DIALECT_KOTLIN) public GradleBuildProjectContributor gradleKtsBuildProjectContributor(KotlinDslGradleBuildWriter buildWriter, - GradleBuild build) { - return new GradleBuildProjectContributor(buildWriter, build, this.indentingWriterFactory, "build.gradle.kts"); + GradleBuild build, ProjectDescription description) { + String buildFileName = gradleBuildFilePath(description, "build.gradle.kts"); + return new GradleBuildProjectContributor(buildWriter, build, this.indentingWriterFactory, buildFileName); + } + + private String gradleBuildFilePath(ProjectDescription description, String buildFileName) { + String module = description.getBuildSystem().getApplicationModuleName(); + return (module != null) ? module + "/" + buildFileName : buildFileName; + } + + @Bean + @ConditionalOnBuildSystem(id = GradleBuildSystem.ID, multiModule = "true") + BuildCustomizer addSubmodulesToGradleBuild(ProjectDescription description) { + return (build) -> build.settings().submodule(description.getBuildSystem().getApplicationModuleName()); } /** diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ApplicationConfigurationProjectGenerationConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ApplicationConfigurationProjectGenerationConfiguration.java index 5fa5e55384..7be03d7e2a 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ApplicationConfigurationProjectGenerationConfiguration.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ApplicationConfigurationProjectGenerationConfiguration.java @@ -17,6 +17,7 @@ package io.spring.initializr.generator.spring.configuration; import io.spring.initializr.generator.buildsystem.Build; +import io.spring.initializr.generator.project.ProjectDescription; import io.spring.initializr.generator.project.ProjectGenerationConfiguration; import io.spring.initializr.metadata.InitializrMetadata; @@ -31,13 +32,20 @@ public class ApplicationConfigurationProjectGenerationConfiguration { @Bean - public ApplicationPropertiesContributor applicationPropertiesContributor() { - return new ApplicationPropertiesContributor(); + public ApplicationPropertiesContributor applicationPropertiesContributor( + ResourcesPathProvider resourcesPathProvider) { + return new ApplicationPropertiesContributor(resourcesPathProvider); } @Bean - public WebFoldersContributor webFoldersContributor(Build build, InitializrMetadata metadata) { - return new WebFoldersContributor(build, metadata); + public WebFoldersContributor webFoldersContributor(Build build, InitializrMetadata metadata, + ResourcesPathProvider resourcesPathProvider) { + return new WebFoldersContributor(build, metadata, resourcesPathProvider); + } + + @Bean + public ResourcesPathProvider resourcesPathProvider(ProjectDescription description) { + return ResourcesPathProvider.forProject(description); } } diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ApplicationPropertiesContributor.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ApplicationPropertiesContributor.java index f87e948566..25b3add14d 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ApplicationPropertiesContributor.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ApplicationPropertiesContributor.java @@ -26,12 +26,22 @@ */ public class ApplicationPropertiesContributor extends SingleResourceProjectContributor { + private static final String defaultResourcePattern = "classpath:configuration/application.properties"; + public ApplicationPropertiesContributor() { - this("classpath:configuration/application.properties"); + this(ResourcesPathProvider.DEFAULT, defaultResourcePattern); } public ApplicationPropertiesContributor(String resourcePattern) { - super("src/main/resources/application.properties", resourcePattern); + this(ResourcesPathProvider.DEFAULT, resourcePattern); + } + + public ApplicationPropertiesContributor(ResourcesPathProvider resourcesPathProvider) { + this(resourcesPathProvider, defaultResourcePattern); + } + + public ApplicationPropertiesContributor(ResourcesPathProvider resourcesPathProvider, String resourcePattern) { + super((root) -> resourcesPathProvider.get(root).resolve("application.properties"), resourcePattern); } } diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ResourcesPathProvider.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ResourcesPathProvider.java new file mode 100644 index 0000000000..f68df068dd --- /dev/null +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/ResourcesPathProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.initializr.generator.spring.configuration; + +import java.nio.file.Path; + +import io.spring.initializr.generator.project.ProjectDescription; + +@FunctionalInterface +interface ResourcesPathProvider { + + ResourcesPathProvider DEFAULT = (root) -> root.resolve("src/main/resources"); + + static ResourcesPathProvider forProject(ProjectDescription description) { + return (root) -> description.getBuildSystem().getMainSource(root, description.getLanguage()) + .getResourcesDirectory(); + } + + Path get(Path projectRoot); + +} diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/WebFoldersContributor.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/WebFoldersContributor.java index d54c364ec6..398c6080e3 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/WebFoldersContributor.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/configuration/WebFoldersContributor.java @@ -39,16 +39,25 @@ public class WebFoldersContributor implements ProjectContributor { private final BuildMetadataResolver buildMetadataResolver; + private final ResourcesPathProvider resourcesPathProvider; + public WebFoldersContributor(Build build, InitializrMetadata metadata) { + this(build, metadata, ResourcesPathProvider.DEFAULT); + } + + public WebFoldersContributor(Build build, InitializrMetadata metadata, + ResourcesPathProvider resourcesPathProvider) { this.build = build; this.buildMetadataResolver = new BuildMetadataResolver(metadata); + this.resourcesPathProvider = resourcesPathProvider; } @Override public void contribute(Path projectRoot) throws IOException { if (this.buildMetadataResolver.hasFacet(this.build, "web")) { - Files.createDirectories(projectRoot.resolve("src/main/resources/templates")); - Files.createDirectories(projectRoot.resolve("src/main/resources/static")); + Path resources = this.resourcesPathProvider.get(projectRoot); + Files.createDirectories(resources.resolve("templates")); + Files.createDirectories(resources.resolve("static")); } } diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/build/gradle/GradleBuildProjectContributorTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/build/gradle/GradleBuildProjectContributorTests.java index a7714eee51..0bb59b73fb 100644 --- a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/build/gradle/GradleBuildProjectContributorTests.java +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/build/gradle/GradleBuildProjectContributorTests.java @@ -50,6 +50,16 @@ void groovyDslGradleBuildIsContributedInProjectStructure(@TempDir Path projectDi assertThat(buildGradle).isRegularFile(); } + @Test + void groovyDslGradleBuildIsContributedInMultiProjectStructure(@TempDir Path projectDir) throws IOException { + String path = "sub/build.gradle"; + GradleBuild build = new GradleBuild(); + new GradleBuildProjectContributor(new GroovyDslGradleBuildWriter(), build, + IndentingWriterFactory.withDefaultSettings(), path).contribute(projectDir); + Path buildGradle = projectDir.resolve(path); + assertThat(buildGradle).isRegularFile(); + } + @Test void groovyDslGradleBuildIsContributedToProject() throws IOException { GradleBuild build = new GradleBuild(); diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/build/gradle/GradleProjectGenerationConfigurationTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/build/gradle/GradleProjectGenerationConfigurationTests.java index 02e643e4f5..8546292917 100644 --- a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/build/gradle/GradleProjectGenerationConfigurationTests.java +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/build/gradle/GradleProjectGenerationConfigurationTests.java @@ -134,6 +134,23 @@ void buildDotGradleIsContributedWhenGeneratingGradleProject() { "}"); // @formatter:on } + static Stream gradleBuildSystemDialects() { + return Stream.of(Arguments.arguments(GradleBuildSystem.DIALECT_GROOVY, "build.gradle"), + Arguments.arguments(GradleBuildSystem.DIALECT_KOTLIN, "build.gradle.kts")); + } + + @ParameterizedTest(name = "Dialect {0}") + @MethodSource("gradleBuildSystemDialects") + void buildScriptIsContributedWhenGeneratingMultiModuleGradleProject(String dialect, String buildFileName) { + MutableProjectDescription description = new MutableProjectDescription(); + description.setPlatformVersion(Version.parse("2.4.0")); + description.setLanguage(new JavaLanguage("11")); + ProjectStructure project = this.projectTester + .withDescriptionCustomizer((d) -> d.setBuildSystem(new GradleBuildSystem(dialect, "service"))) + .generate(description); + assertThat(project).containsFiles("service/" + buildFileName); + } + @Test void warPluginIsAppliedWhenBuildingProjectThatUsesWarPackaging() { MutableProjectDescription description = new MutableProjectDescription(); diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/ApplicationConfigurationProjectGenerationConfigurationTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/ApplicationConfigurationProjectGenerationConfigurationTests.java new file mode 100644 index 0000000000..ea2d176002 --- /dev/null +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/ApplicationConfigurationProjectGenerationConfigurationTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.initializr.generator.spring.configuration; + +import java.nio.file.Path; + +import io.spring.initializr.generator.buildsystem.gradle.GradleBuildSystem; +import io.spring.initializr.generator.language.java.JavaLanguage; +import io.spring.initializr.generator.project.MutableProjectDescription; +import io.spring.initializr.generator.spring.build.gradle.GradleProjectGenerationConfiguration; +import io.spring.initializr.generator.test.InitializrMetadataTestBuilder; +import io.spring.initializr.generator.test.project.ProjectAssetTester; +import io.spring.initializr.generator.test.project.ProjectStructure; +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.metadata.InitializrMetadata; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ApplicationConfigurationProjectGenerationConfiguration} + * + * @author Linda Navarette + */ +class ApplicationConfigurationProjectGenerationConfigurationTests { + + private ProjectAssetTester projectTester; + + @BeforeEach + void setup(@TempDir Path directory) { + this.projectTester = new ProjectAssetTester().withIndentingWriterFactory() + .withConfiguration(ApplicationConfigurationProjectGenerationConfiguration.class, + GradleProjectGenerationConfiguration.class) + .withDirectory(directory) + .withBean(InitializrMetadata.class, () -> InitializrMetadataTestBuilder.withDefaults().build()); + } + + @Test + void applicationConfigurationResourcesAreIncludedInSingleModuleBuild() { + MutableProjectDescription description = new MutableProjectDescription(); + description.setPlatformVersion(Version.parse("2.4.0")); + description.setLanguage(new JavaLanguage()); + description.setBuildSystem(new GradleBuildSystem()); + ProjectStructure project = this.projectTester.generate(description); + assertThat(project).containsFiles("src/main/resources/application.properties"); + } + + @Test + void applicationConfigurationResourcesAreIncludedInMultiModuleBuild() { + MutableProjectDescription description = new MutableProjectDescription(); + description.setPlatformVersion(Version.parse("2.4.0")); + description.setLanguage(new JavaLanguage()); + description.setBuildSystem(new GradleBuildSystem(GradleBuildSystem.DIALECT_GROOVY, "foo")); + ProjectStructure project = this.projectTester.generate(description); + assertThat(project).containsFiles("foo/src/main/resources/application.properties"); + } + +} diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/ApplicationPropertiesContributorTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/ApplicationPropertiesContributorTests.java index 8930c3c1f0..4b79101bf6 100644 --- a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/ApplicationPropertiesContributorTests.java +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/ApplicationPropertiesContributorTests.java @@ -44,4 +44,11 @@ void applicationConfigurationWithDefaultSettings() throws IOException { .isEmpty(); } + @Test + void applicationConfigurationWithCustomResourcesDirectory() throws IOException { + Path projectDir = Files.createTempDirectory(this.directory, "project-"); + new ApplicationPropertiesContributor((root) -> root.resolve("foo")).contribute(projectDir); + assertThat(new ProjectStructure(projectDir)).textFile("foo/application.properties").lines().isEmpty(); + } + } diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/WebFoldersContributorTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/WebFoldersContributorTests.java index c33d2c8015..8b91081d55 100644 --- a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/WebFoldersContributorTests.java +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/configuration/WebFoldersContributorTests.java @@ -81,7 +81,8 @@ private Build createBuild(InitializrMetadata metadata) { } private Path contribute(Build build, InitializrMetadata metadata) throws IOException { - new WebFoldersContributor(build, metadata).contribute(this.projectDir); + new WebFoldersContributor(build, metadata, (dir) -> dir.resolve("src/main/resources")) + .contribute(this.projectDir); return this.projectDir; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/BuildSettings.java b/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/BuildSettings.java index d1d36a1886..81d13f9c90 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/BuildSettings.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/BuildSettings.java @@ -16,6 +16,9 @@ package io.spring.initializr.generator.buildsystem; +import java.util.ArrayList; +import java.util.List; + /** * General build settings. * @@ -29,10 +32,13 @@ public class BuildSettings { private final String version; + private final List submodules; + protected BuildSettings(Builder builder) { this.group = builder.group; this.artifact = builder.artifact; this.version = builder.version; + this.submodules = builder.submodules; } /** @@ -59,6 +65,14 @@ public String getVersion() { return this.version; } + /** + * Return the submodules of the project. + * @return the submodules or {@code null} + */ + public List getSubmodules() { + return this.submodules; + } + /** * Builder for build settings. * @@ -72,6 +86,8 @@ public abstract static class Builder> { private String version = "0.0.1-SNAPSHOT"; + private final List submodules = new ArrayList<>(); + protected Builder() { } @@ -105,6 +121,16 @@ public B version(String version) { return self(); } + /** + * Adds a submodule to the project. + * @param moduleName the name of the submodule + * @return this for method chaining + */ + public B submodule(String moduleName) { + this.submodules.add(moduleName); + return self(); + } + @SuppressWarnings("unchecked") protected B self() { return (B) this; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/BuildSystem.java b/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/BuildSystem.java index 586cafbcce..13337a2c3e 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/BuildSystem.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/BuildSystem.java @@ -46,6 +46,33 @@ default String dialect() { return null; } + /** + * Whether the build is a multi module build. + * @return {@code true} if multi module build + */ + default boolean isMultiModuleBuild() { + return getApplicationModuleName() != null; + } + + /** + * Name of the application module; or {@code null} if a single module build. + * @return name of the application module + */ + default String getApplicationModuleName() { + return null; + } + + /** + * Path of the application module; this may differ from the project root if the + * application is multi-module. + * @param projectRoot the root of the project structure + * @return path for the application project + */ + default Path getApplicationModuleRoot(Path projectRoot) { + String sub = getApplicationModuleName(); + return (sub != null) ? projectRoot.resolve(sub) : projectRoot; + } + /** * Returns a {@link SourceStructure} for main sources. * @param projectRoot the root of the project structure @@ -53,7 +80,7 @@ default String dialect() { * @return a {@link SourceStructure} for main assets */ default SourceStructure getMainSource(Path projectRoot, Language language) { - return new SourceStructure(projectRoot.resolve("src/main/"), language); + return new SourceStructure(getApplicationModuleRoot(projectRoot).resolve("src/main/"), language); } /** @@ -63,7 +90,7 @@ default SourceStructure getMainSource(Path projectRoot, Language language) { * @return a {@link SourceStructure} for test assets */ default SourceStructure getTestSource(Path projectRoot, Language language) { - return new SourceStructure(projectRoot.resolve("src/test/"), language); + return new SourceStructure(getApplicationModuleRoot(projectRoot).resolve("src/test/"), language); } static BuildSystem forId(String id) { diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/gradle/GradleBuildSystem.java b/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/gradle/GradleBuildSystem.java index 14faaf800c..e66e5ea2bd 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/gradle/GradleBuildSystem.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/gradle/GradleBuildSystem.java @@ -42,12 +42,19 @@ public final class GradleBuildSystem implements BuildSystem { private final String dialect; + private final String applicationModuleName; + public GradleBuildSystem() { this(DIALECT_GROOVY); } public GradleBuildSystem(String dialect) { + this(dialect, null); + } + + public GradleBuildSystem(String dialect, String applicationModuleName) { this.dialect = dialect; + this.applicationModuleName = applicationModuleName; } @Override @@ -60,6 +67,11 @@ public String dialect() { return this.dialect; } + @Override + public String getApplicationModuleName() { + return this.applicationModuleName; + } + @Override public String toString() { return id(); diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/gradle/GradleSettingsWriter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/gradle/GradleSettingsWriter.java index f686a0c1c8..f81ab70ad9 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/gradle/GradleSettingsWriter.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/buildsystem/gradle/GradleSettingsWriter.java @@ -40,6 +40,7 @@ public abstract class GradleSettingsWriter { public final void writeTo(IndentingWriter writer, GradleBuild build) { writePluginManagement(writer, build); writer.println("rootProject.name = " + wrapWithQuotes(build.getSettings().getArtifact())); + build.getSettings().getSubmodules().forEach((s) -> writer.println("include(" + wrapWithQuotes(s) + ")")); } private void writePluginManagement(IndentingWriter writer, GradleBuild build) { diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystem.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystem.java index 2f2f0fc35d..d3867e1f3b 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystem.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystem.java @@ -60,4 +60,12 @@ */ String dialect() default ""; + /** + * Whether the {@link BuildSystem} is configured for multi module builds. Use + * {@code true} to indicate multi module, and {@code false} to indicate single module. + * When not specified, either will be matched. + * @return whether the build is multi module + */ + String multiModule() default ""; + } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnBuildSystemCondition.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnBuildSystemCondition.java index 9f2c21fd8a..cc561e83ae 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnBuildSystemCondition.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnBuildSystemCondition.java @@ -39,14 +39,15 @@ protected boolean matches(ProjectDescription description, ConditionContext conte .getAllAnnotationAttributes(ConditionalOnBuildSystem.class.getName()); String buildSystemId = (String) attributes.getFirst("value"); String dialect = (String) attributes.getFirst("dialect"); + String multiModule = (String) attributes.getFirst("multiModule"); BuildSystem buildSystem = description.getBuildSystem(); - if (buildSystem.id().equals(buildSystemId)) { - if (StringUtils.hasText(dialect)) { - return dialect.equals(buildSystem.dialect()); - } - return true; - } - return false; + boolean dialectMatches = matches(dialect, buildSystem.dialect()); + boolean multiModuleMatches = matches(multiModule, String.valueOf(buildSystem.isMultiModuleBuild())); + return buildSystem.id().equals(buildSystemId) && dialectMatches && multiModuleMatches; + } + + private boolean matches(String target, String actual) { + return !StringUtils.hasText(target) || target.equals(actual); } } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/SingleResourceProjectContributor.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/SingleResourceProjectContributor.java index 283bdcebe8..2bb4326aab 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/SingleResourceProjectContributor.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/SingleResourceProjectContributor.java @@ -20,6 +20,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.function.Function; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @@ -36,7 +37,7 @@ public class SingleResourceProjectContributor implements ProjectContributor { private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - private final String relativePath; + private final Function pathProvider; private final String resourcePattern; @@ -49,13 +50,17 @@ public class SingleResourceProjectContributor implements ProjectContributor { * @see PathMatchingResourcePatternResolver#getResource(String) */ public SingleResourceProjectContributor(String relativePath, String resourcePattern) { - this.relativePath = relativePath; + this((root) -> root.resolve(relativePath), resourcePattern); + } + + public SingleResourceProjectContributor(Function pathProvider, String resourcePattern) { + this.pathProvider = pathProvider; this.resourcePattern = resourcePattern; } @Override public void contribute(Path projectRoot) throws IOException { - Path output = projectRoot.resolve(this.relativePath); + Path output = this.pathProvider.apply(projectRoot); if (!Files.exists(output)) { Files.createDirectories(output.getParent()); Files.createFile(output); diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/BuildSystemTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/BuildSystemTests.java index 90037910a2..ad5d66f33b 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/BuildSystemTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/BuildSystemTests.java @@ -66,6 +66,33 @@ void defaultTestSource(@TempDir Path directory) { assertThat(testCodeStructure.getSourcesDirectory()).isEqualTo(directory.resolve("src/test/kotlin")); } + @Test + void gradleMultiModuleApplicationRoot(@TempDir Path directory) { + Path applicationModuleRoot = new GradleBuildSystem(GradleBuildSystem.DIALECT_GROOVY, "my-project") + .getApplicationModuleRoot(directory); + assertThat(applicationModuleRoot).isEqualTo(directory.resolve("my-project")); + } + + @Test + void gradleMultiModuleMainSource(@TempDir Path directory) { + SourceStructure mainCodeStructure = new GradleBuildSystem(GradleBuildSystem.DIALECT_GROOVY, "my-project") + .getMainSource(directory, new JavaLanguage()); + assertThat(mainCodeStructure.getRootDirectory()).isEqualTo(directory.resolve("my-project/src/main")); + assertThat(mainCodeStructure.getSourcesDirectory()).isEqualTo(directory.resolve("my-project/src/main/java")); + assertThat(mainCodeStructure.getResourcesDirectory()) + .isEqualTo(directory.resolve("my-project/src/main/resources")); + } + + @Test + void gradleMultiModuleTestSource(@TempDir Path directory) { + SourceStructure mainCodeStructure = new GradleBuildSystem(GradleBuildSystem.DIALECT_GROOVY, "my-project") + .getTestSource(directory, new JavaLanguage()); + assertThat(mainCodeStructure.getRootDirectory()).isEqualTo(directory.resolve("my-project/src/test")); + assertThat(mainCodeStructure.getSourcesDirectory()).isEqualTo(directory.resolve("my-project/src/test/java")); + assertThat(mainCodeStructure.getResourcesDirectory()) + .isEqualTo(directory.resolve("my-project/src/test/resources")); + } + @Test void unknownBuildSystem() { assertThatIllegalStateException().isThrownBy(() -> BuildSystem.forId("unknown")) diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/gradle/GroovyDslGradleSettingsWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/gradle/GroovyDslGradleSettingsWriterTests.java index 46113f375f..d57cb7eaf9 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/gradle/GroovyDslGradleSettingsWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/gradle/GroovyDslGradleSettingsWriterTests.java @@ -109,6 +109,14 @@ void artifactIdShouldBeUsedAsTheRootProjectName() { assertThat(lines).containsSequence("rootProject.name = 'my-application'"); } + @Test + void submoduleShouldBeIncluded() { + GradleBuild build = new GradleBuild(); + build.settings().artifact("my-application").submodule("submodule"); + List lines = generateSettings(build); + assertThat(lines).containsSequence("include('submodule')"); + } + private List generateSettings(GradleBuild build) { GradleSettingsWriter writer = new GroovyDslGradleSettingsWriter(); StringWriter out = new StringWriter(); diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/gradle/KotlinDslGradleSettingsWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/gradle/KotlinDslGradleSettingsWriterTests.java index 8052cf9e6f..86cf31ebc6 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/gradle/KotlinDslGradleSettingsWriterTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/buildsystem/gradle/KotlinDslGradleSettingsWriterTests.java @@ -108,6 +108,14 @@ void artifactIdShouldBeUsedAsTheRootProjectName() { assertThat(lines).containsSequence("rootProject.name = \"my-application\""); } + @Test + void submoduleShouldBeIncluded() { + GradleBuild build = new GradleBuild(); + build.settings().artifact("my-application").submodule("submodule"); + List lines = generateSettings(build); + assertThat(lines).containsSequence("include(\"submodule\")"); + } + private List generateSettings(GradleBuild build) { GradleSettingsWriter writer = new KotlinDslGradleSettingsWriter(); StringWriter out = new StringWriter(); diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystemTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystemTests.java index 9bc5fb35a5..df44fc64cf 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystemTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystemTests.java @@ -59,6 +59,14 @@ void conditionalOnGradleWithKotlinDialectMatchesWhenGradleBuildSystemUsesKotlinD "gradleKotlin"); } + @Test + void conditionalOnGradleWithMultiModuleMatchesWhenGradleBuildSystemUsesMultiModuleConfiguration() { + MutableProjectDescription description = new MutableProjectDescription(); + description.setBuildSystem(new GradleBuildSystem(GradleBuildSystem.DIALECT_GROOVY, "test")); + assertThat(candidatesFor(description, BuildSystemTestConfiguration.class)).containsOnlyKeys("gradle", + "multiModuleGradleAnyDialect"); + } + private Map candidatesFor(MutableProjectDescription description, Class... extraConfigurations) { try (ProjectGenerationContext context = new ProjectGenerationContext()) { context.registerBean(ProjectDescription.class, () -> description); @@ -95,6 +103,12 @@ String gradleKotlin() { return "testGradleKotlinDialect"; } + @Bean + @ConditionalOnBuildSystem(id = "gradle", multiModule = "true") + String multiModuleGradleAnyDialect() { + return "multiModuleGradleAnyDialect"; + } + } }