diff --git a/CHANGELOG.md b/CHANGELOG.md index c47bcc7..776d704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Java Module Dependencies Gradle Plugin - Changelog ## Version 1.8 +* [#136](https://github.com/gradlex-org/java-module-dependencies/pull/136) Support hierarchical project paths in Settings DSL * [#141](https://github.com/gradlex-org/java-module-dependencies/pull/141) Introduce `org.gradlex.java-module-dependencies.register-help-tasks` property * [#127](https://github.com/gradlex-org/java-module-dependencies/issues/127) Less configuration cache misses when modifying `module-info.java` (Thanks [TheGoesen](https://github.com/TheGoesen)) * [#128](https://github.com/gradlex-org/java-module-dependencies/issues/128) Less configuration cache misses when using Settings plugin (Thanks [TheGoesen](https://github.com/TheGoesen)) diff --git a/README.MD b/README.MD index b068540..00e90f1 100644 --- a/README.MD +++ b/README.MD @@ -129,6 +129,19 @@ javaModules { // use instead of 'include(...)' } ``` +If you need more control over the properties of a Gradle subproject, in particular to define a nested project path, +you can still use Gradle's `include(...)` and then register the subproject with this plugin. + +``` +include(":project:with:custom:path") +javaModules { + module(project(":project:with:custom:path")) { + group = "org.example" // define group early so that all subprojects know all groups + plugin("java-library") // apply plugin to the Module's subproject to omit 'build.gradle' + } +} +``` + ## Project structure definition when using this plugin as Project Plugin In this setup, subprojects with Java Modules are configured as in any traditional Gradle build: by using the diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java index 2f23cfd..2fdea24 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java @@ -20,7 +20,6 @@ import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; -import org.gradle.api.provider.Provider; import javax.inject.Inject; import java.io.File; @@ -80,8 +79,7 @@ public void module(String subDirectory, Action action) { } Module addModule(String subDirectory) { - Module module = getObjects().newInstance(Module.class, root); - module.getDirectory().convention(subDirectory); + Module module = getObjects().newInstance(Module.class, new File(root, subDirectory)); module.getGroup().convention(getGroup()); module.getPlugins().addAll(getPlugins()); return module; diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java index 8ce49e5..3899ee9 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java @@ -72,10 +72,27 @@ public void module(String directory) { * Register and configure Module located in the given folder, relative to the build root directory. */ public void module(String directory, Action action) { - Module module = getObjects().newInstance(Module.class, settings.getRootDir()); - module.getDirectory().set(directory); + Module module = getObjects().newInstance(Module.class, new File(settings.getRootDir(), directory)); action.execute(module); - includeModule(module, new File(settings.getRootDir(), module.getDirectory().get())); + includeModule(module, new File(settings.getRootDir(), directory)); + } + + /** + * {@link JavaModulesExtension#module(ProjectDescriptor, Action)} + */ + public void module(ProjectDescriptor project) { + module(project, m -> {}); + } + + /** + * Register and configure Module already registered as project by an 'include' statement. + */ + public void module(ProjectDescriptor project, Action action) { + Module module = getObjects().newInstance(Module.class, project.getProjectDir()); + module.getArtifact().set(project.getName()); + module.getArtifact().finalizeValue(); // finalize, as the project name can no longer be changed + action.execute(module); + configureModule(module, project); } /** @@ -94,7 +111,7 @@ public void directory(String directory, Action action) { action.execute(moduleDirectory); for (Module module : moduleDirectory.customizedModules.values()) { - includeModule(module, new File(modulesDirectory, module.getDirectory().get())); + includeModule(module, module.directory); } Provider> listProvider = getProviders().of(ValueModuleDirectoryListing.class, spec -> { spec.getParameters().getExclusions().set(moduleDirectory.getExclusions()); @@ -119,7 +136,7 @@ public void versions(String directory) { String projectName = Paths.get(directory).getFileName().toString(); settings.include(projectName); settings.project(":" + projectName).setProjectDir(new File(settings.getRootDir(), directory)); - settings.getGradle().getLifecycle().beforeProject(new ApplyJavaModuleVersionsPluginAction(projectName)); + settings.getGradle().getLifecycle().beforeProject(new ApplyJavaModuleVersionsPluginAction(":" + projectName)); } private void includeModule(Module module, File projectDir) { @@ -128,28 +145,32 @@ private void includeModule(Module module, File projectDir) { ProjectDescriptor project = settings.project(":" + artifact); project.setProjectDir(projectDir); + configureModule(module, project); + } + + private void configureModule(Module module, ProjectDescriptor project) { String mainModuleName = null; - for (String path : module.getModuleInfoPaths().get()) { - ModuleInfo moduleInfo = moduleInfoCache.put(projectDir, path, - module.getArtifact().get(), module.getGroup(), settings.getProviders()); - if (path.contains("/main/")) { + for (String moduleInfoPath : module.getModuleInfoPaths().get()) { + ModuleInfo moduleInfo = moduleInfoCache.put(project.getProjectDir(), moduleInfoPath, + project.getPath(), module.getArtifact().get(), module.getGroup(), settings.getProviders()); + if (moduleInfoPath.contains("/main/")) { mainModuleName = moduleInfo.getModuleName(); } } String group = module.getGroup().getOrNull(); List plugins = module.getPlugins().get(); - moduleProjects.add(new ModuleProject(artifact, group, plugins, mainModuleName)); + moduleProjects.add(new ModuleProject(project.getPath(), group, plugins, mainModuleName)); } private static class ModuleProject { - private final String artifact; + private final String path; private final String group; private final List plugins; private final String mainModuleName; - public ModuleProject(String artifact, String group, List plugins, String mainModuleName) { - this.artifact = artifact; + public ModuleProject(String path, String group, List plugins, String mainModuleName) { + this.path = path; this.group = group; this.plugins = plugins; this.mainModuleName = mainModuleName; @@ -170,7 +191,7 @@ public ApplyPluginsAction(List moduleProjects, ModuleInfoCache mo @Override public void execute(Project project) { for (ModuleProject m : moduleProjects) { - if (project.getName().equals(m.artifact)) { + if (project.getPath().equals(m.path)) { if (m.group != null) project.setGroup(m.group); project.getPlugins().apply(JavaModuleDependenciesPlugin.class); project.getExtensions().getByType(JavaModuleDependenciesExtension.class).getModuleInfoCache().set(moduleInfoCache); @@ -187,15 +208,15 @@ public void execute(Project project) { @NonNullApi private static class ApplyJavaModuleVersionsPluginAction implements IsolatedAction { - private final String projectName; + private final String projectPath; - public ApplyJavaModuleVersionsPluginAction(String projectName) { - this.projectName = projectName; + public ApplyJavaModuleVersionsPluginAction(String projectPath) { + this.projectPath = projectPath; } @Override public void execute(Project project) { - if (projectName.equals(project.getName())) { + if (projectPath.equals(project.getPath())) { project.getPlugins().apply(JavaPlatformPlugin.class); project.getPlugins().apply(JavaModuleVersionsPlugin.class); project.getExtensions().getByType(JavaPlatformExtension.class).allowDependencies(); diff --git a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java index 5f98e7c..ff62bcb 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java @@ -28,11 +28,6 @@ public abstract class Module { - /** - * The directory, relative to the build root directory, in which the Module is located. - */ - public abstract Property getDirectory(); - /** * The 'artifact' name of the Module. This corresponds to the Gradle subproject name. If the Module is published * to a Maven repository, this is the 'artifact' in the 'group:artifact' identifier to address the published Jar. @@ -58,14 +53,17 @@ public abstract class Module { */ public abstract ListProperty getPlugins(); + File directory; + @Inject - public Module(File root) { - getArtifact().convention(getDirectory().map(f -> Paths.get(f).getFileName().toString())); - getModuleInfoPaths().convention(getDirectory().map(projectDir -> listChildren(root, projectDir + "/src") + public Module(File directory) { + this.directory = directory; + getArtifact().convention(directory.getName()); + getModuleInfoPaths().convention(listSrcChildren() .map(srcDir -> new File(srcDir, "java/module-info.java")) .filter(File::exists) .map(moduleInfo -> "src/" + moduleInfo.getParentFile().getParentFile().getName() + "/java") - .collect(Collectors.toList()))); + .collect(Collectors.toList())); } /** @@ -76,8 +74,8 @@ public void plugin(String id) { getPlugins().add(id); } - private Stream listChildren(File root, String projectDir) { - File[] children = new File(root, projectDir).listFiles(); + private Stream listSrcChildren() { + File[] children = new File(directory, "src").listFiles(); return children == null ? Stream.empty() : Arrays.stream(children); } } diff --git a/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java b/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java index c939408..1c97b24 100644 --- a/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java +++ b/src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java @@ -81,11 +81,11 @@ public File getFolder(SourceSet sourceSet, ProviderFactory providers) { * @param projectRoot the project that should hold a Java module * @return parsed module-info.java for the given project assuming a standard Java project layout */ - public ModuleInfo put(File projectRoot, String moduleInfoPath, String artifact, Provider group, ProviderFactory providers) { + public ModuleInfo put(File projectRoot, String moduleInfoPath, String projectPath, String artifact, Provider group, ProviderFactory providers) { File folder = new File(projectRoot, moduleInfoPath); if (maybePutModuleInfo(folder, providers)) { ModuleInfo thisModuleInfo = moduleInfo.get(folder); - moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), ":" + artifact); + moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), projectPath); Path parentDirectory = Paths.get(moduleInfoPath).getParent(); String capabilitySuffix = parentDirectory == null ? null : sourceSetToCapabilitySuffix(parentDirectory.getFileName().toString()); if (capabilitySuffix != null) { diff --git a/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginIncludeTest.groovy b/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginIncludeTest.groovy new file mode 100644 index 0000000..b092db0 --- /dev/null +++ b/src/test/groovy/org/gradlex/javamodule/dependencies/test/initialization/SettingsPluginIncludeTest.groovy @@ -0,0 +1,113 @@ +package org.gradlex.javamodule.dependencies.test.initialization + +import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild +import spock.lang.Specification + +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class SettingsPluginIncludeTest extends Specification { + + @Delegate + GradleBuild build = new GradleBuild() + + def setup() { + settingsFile.text = ''' + plugins { id("org.gradlex.java-module-dependencies") } + ''' + appBuildFile.delete() + libBuildFile.delete() + } + + def "can define included subprojects as modules"() { + given: + settingsFile << ''' + include(":project:with:custom:path") + javaModules { + module(project(":project:with:custom:path")) { + group = "org.example" + plugin("java-library") + } + module(project(":project:with:custom")) { + group = "org.example" + plugin("java-library") + } + } + ''' + + file('project/with/custom/path/src/main/java/module-info.java') << 'module abc.liba { }' + file('project/with/custom/src/main/java/module-info.java') << '''module abc.libb { + requires abc.liba; + }''' + + when: + def result = runner(':project:with:custom:compileJava').build() + + then: + result.task(":project:with:custom:path:compileJava").outcome == SUCCESS + result.task(":project:with:custom:compileJava").outcome == SUCCESS + } + + def "can define included subprojects with custom project directory as modules"() { + given: + settingsFile << ''' + include(":project:with:custom:path") + project(":project:with:custom:path").projectDir = file("lib") + project(":project:with:custom").projectDir = file("app") + javaModules { + module(project(":project:with:custom:path")) { + group = "org.example" + plugin("java-library") + } + module(project(":project:with:custom")) { + group = "org.example" + plugin("java-library") + } + } + ''' + + file("project/with").mkdirs() + libModuleInfoFile << 'module abc.lib { }' + appModuleInfoFile << '''module abc.app { + requires abc.lib; + }''' + + when: + def result = runner(':project:with:custom:jar').build() + + then: + result.task(":project:with:custom:path:compileJava").outcome == SUCCESS + result.task(":project:with:custom:compileJava").outcome == SUCCESS + file("lib/build/libs/path.jar").exists() + file("app/build/libs/custom.jar").exists() + } + + def "projects with same name but different paths are supported"() { + given: + settingsFile << ''' + include(":app1:feature1:data") + include(":app1:feature2:data") + + rootProject.children.forEach { appContainer -> + appContainer.children.forEach { featureContainer -> + featureContainer.children.forEach { module -> + javaModules.module(module) { plugin("java-library") } + } + } + } + ''' + + file('app1/feature1/data/src/main/java/module-info.java') << 'module f1x.data { }' + file('app1/feature2/data/src/main/java/module-info.java') << '''module f2x.data { + requires f1x.data; + }''' + + when: + def result = runner(':app1:feature2:data:jar').build() + + then: + result.task(":app1:feature1:data:jar").outcome == SUCCESS + result.task(":app1:feature2:data:jar").outcome == SUCCESS + file("app1/feature1/data/build/libs/data.jar").exists() + file("app1/feature2/data/build/libs/data.jar").exists() + } +}