Skip to content

Commit 4315904

Browse files
authored
Support using Gradle's 'include' in settings plugin (#143)
This allows to configure project paths individually via the native include("...") statement while still using other functionality provided by the settings plugin.
1 parent a870abc commit 4315904

File tree

7 files changed

+178
-34
lines changed

7 files changed

+178
-34
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Java Module Dependencies Gradle Plugin - Changelog
22

33
## Version 1.8
4+
* [#136](https://github.com/gradlex-org/java-module-dependencies/pull/136) Support hierarchical project paths in Settings DSL
45
* [#141](https://github.com/gradlex-org/java-module-dependencies/pull/141) Introduce `org.gradlex.java-module-dependencies.register-help-tasks` property
56
* [#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))
67
* [#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))

README.MD

+13
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ javaModules { // use instead of 'include(...)'
129129
}
130130
```
131131
132+
If you need more control over the properties of a Gradle subproject, in particular to define a nested project path,
133+
you can still use Gradle's `include(...)` and then register the subproject with this plugin.
134+
135+
```
136+
include(":project:with:custom:path")
137+
javaModules {
138+
module(project(":project:with:custom:path")) {
139+
group = "org.example" // define group early so that all subprojects know all groups
140+
plugin("java-library") // apply plugin to the Module's subproject to omit 'build.gradle'
141+
}
142+
}
143+
```
144+
132145
## Project structure definition when using this plugin as Project Plugin
133146
134147
In this setup, subprojects with Java Modules are configured as in any traditional Gradle build: by using the

src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import org.gradle.api.model.ObjectFactory;
2121
import org.gradle.api.provider.ListProperty;
2222
import org.gradle.api.provider.Property;
23-
import org.gradle.api.provider.Provider;
2423

2524
import javax.inject.Inject;
2625
import java.io.File;
@@ -80,8 +79,7 @@ public void module(String subDirectory, Action<Module> action) {
8079
}
8180

8281
Module addModule(String subDirectory) {
83-
Module module = getObjects().newInstance(Module.class, root);
84-
module.getDirectory().convention(subDirectory);
82+
Module module = getObjects().newInstance(Module.class, new File(root, subDirectory));
8583
module.getGroup().convention(getGroup());
8684
module.getPlugins().addAll(getPlugins());
8785
return module;

src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java

+39-18
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,27 @@ public void module(String directory) {
7272
* Register and configure Module located in the given folder, relative to the build root directory.
7373
*/
7474
public void module(String directory, Action<Module> action) {
75-
Module module = getObjects().newInstance(Module.class, settings.getRootDir());
76-
module.getDirectory().set(directory);
75+
Module module = getObjects().newInstance(Module.class, new File(settings.getRootDir(), directory));
7776
action.execute(module);
78-
includeModule(module, new File(settings.getRootDir(), module.getDirectory().get()));
77+
includeModule(module, new File(settings.getRootDir(), directory));
78+
}
79+
80+
/**
81+
* {@link JavaModulesExtension#module(ProjectDescriptor, Action)}
82+
*/
83+
public void module(ProjectDescriptor project) {
84+
module(project, m -> {});
85+
}
86+
87+
/**
88+
* Register and configure Module already registered as project by an 'include' statement.
89+
*/
90+
public void module(ProjectDescriptor project, Action<Module> action) {
91+
Module module = getObjects().newInstance(Module.class, project.getProjectDir());
92+
module.getArtifact().set(project.getName());
93+
module.getArtifact().finalizeValue(); // finalize, as the project name can no longer be changed
94+
action.execute(module);
95+
configureModule(module, project);
7996
}
8097

8198
/**
@@ -94,7 +111,7 @@ public void directory(String directory, Action<Directory> action) {
94111
action.execute(moduleDirectory);
95112

96113
for (Module module : moduleDirectory.customizedModules.values()) {
97-
includeModule(module, new File(modulesDirectory, module.getDirectory().get()));
114+
includeModule(module, module.directory);
98115
}
99116
Provider<List<String>> listProvider = getProviders().of(ValueModuleDirectoryListing.class, spec -> {
100117
spec.getParameters().getExclusions().set(moduleDirectory.getExclusions());
@@ -119,7 +136,7 @@ public void versions(String directory) {
119136
String projectName = Paths.get(directory).getFileName().toString();
120137
settings.include(projectName);
121138
settings.project(":" + projectName).setProjectDir(new File(settings.getRootDir(), directory));
122-
settings.getGradle().getLifecycle().beforeProject(new ApplyJavaModuleVersionsPluginAction(projectName));
139+
settings.getGradle().getLifecycle().beforeProject(new ApplyJavaModuleVersionsPluginAction(":" + projectName));
123140
}
124141

125142
private void includeModule(Module module, File projectDir) {
@@ -128,28 +145,32 @@ private void includeModule(Module module, File projectDir) {
128145
ProjectDescriptor project = settings.project(":" + artifact);
129146
project.setProjectDir(projectDir);
130147

148+
configureModule(module, project);
149+
}
150+
151+
private void configureModule(Module module, ProjectDescriptor project) {
131152
String mainModuleName = null;
132-
for (String path : module.getModuleInfoPaths().get()) {
133-
ModuleInfo moduleInfo = moduleInfoCache.put(projectDir, path,
134-
module.getArtifact().get(), module.getGroup(), settings.getProviders());
135-
if (path.contains("/main/")) {
153+
for (String moduleInfoPath : module.getModuleInfoPaths().get()) {
154+
ModuleInfo moduleInfo = moduleInfoCache.put(project.getProjectDir(), moduleInfoPath,
155+
project.getPath(), module.getArtifact().get(), module.getGroup(), settings.getProviders());
156+
if (moduleInfoPath.contains("/main/")) {
136157
mainModuleName = moduleInfo.getModuleName();
137158
}
138159
}
139160

140161
String group = module.getGroup().getOrNull();
141162
List<String> plugins = module.getPlugins().get();
142-
moduleProjects.add(new ModuleProject(artifact, group, plugins, mainModuleName));
163+
moduleProjects.add(new ModuleProject(project.getPath(), group, plugins, mainModuleName));
143164
}
144165

145166
private static class ModuleProject {
146-
private final String artifact;
167+
private final String path;
147168
private final String group;
148169
private final List<String> plugins;
149170
private final String mainModuleName;
150171

151-
public ModuleProject(String artifact, String group, List<String> plugins, String mainModuleName) {
152-
this.artifact = artifact;
172+
public ModuleProject(String path, String group, List<String> plugins, String mainModuleName) {
173+
this.path = path;
153174
this.group = group;
154175
this.plugins = plugins;
155176
this.mainModuleName = mainModuleName;
@@ -170,7 +191,7 @@ public ApplyPluginsAction(List<ModuleProject> moduleProjects, ModuleInfoCache mo
170191
@Override
171192
public void execute(Project project) {
172193
for (ModuleProject m : moduleProjects) {
173-
if (project.getName().equals(m.artifact)) {
194+
if (project.getPath().equals(m.path)) {
174195
if (m.group != null) project.setGroup(m.group);
175196
project.getPlugins().apply(JavaModuleDependenciesPlugin.class);
176197
project.getExtensions().getByType(JavaModuleDependenciesExtension.class).getModuleInfoCache().set(moduleInfoCache);
@@ -187,15 +208,15 @@ public void execute(Project project) {
187208
@NonNullApi
188209
private static class ApplyJavaModuleVersionsPluginAction implements IsolatedAction<Project> {
189210

190-
private final String projectName;
211+
private final String projectPath;
191212

192-
public ApplyJavaModuleVersionsPluginAction(String projectName) {
193-
this.projectName = projectName;
213+
public ApplyJavaModuleVersionsPluginAction(String projectPath) {
214+
this.projectPath = projectPath;
194215
}
195216

196217
@Override
197218
public void execute(Project project) {
198-
if (projectName.equals(project.getName())) {
219+
if (projectPath.equals(project.getPath())) {
199220
project.getPlugins().apply(JavaPlatformPlugin.class);
200221
project.getPlugins().apply(JavaModuleVersionsPlugin.class);
201222
project.getExtensions().getByType(JavaPlatformExtension.class).allowDependencies();

src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java

+9-11
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@
2828

2929
public abstract class Module {
3030

31-
/**
32-
* The directory, relative to the build root directory, in which the Module is located.
33-
*/
34-
public abstract Property<String> getDirectory();
35-
3631
/**
3732
* The 'artifact' name of the Module. This corresponds to the Gradle subproject name. If the Module is published
3833
* 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 {
5853
*/
5954
public abstract ListProperty<String> getPlugins();
6055

56+
File directory;
57+
6158
@Inject
62-
public Module(File root) {
63-
getArtifact().convention(getDirectory().map(f -> Paths.get(f).getFileName().toString()));
64-
getModuleInfoPaths().convention(getDirectory().map(projectDir -> listChildren(root, projectDir + "/src")
59+
public Module(File directory) {
60+
this.directory = directory;
61+
getArtifact().convention(directory.getName());
62+
getModuleInfoPaths().convention(listSrcChildren()
6563
.map(srcDir -> new File(srcDir, "java/module-info.java"))
6664
.filter(File::exists)
6765
.map(moduleInfo -> "src/" + moduleInfo.getParentFile().getParentFile().getName() + "/java")
68-
.collect(Collectors.toList())));
66+
.collect(Collectors.toList()));
6967
}
7068

7169
/**
@@ -76,8 +74,8 @@ public void plugin(String id) {
7674
getPlugins().add(id);
7775
}
7876

79-
private Stream<File> listChildren(File root, String projectDir) {
80-
File[] children = new File(root, projectDir).listFiles();
77+
private Stream<File> listSrcChildren() {
78+
File[] children = new File(directory, "src").listFiles();
8179
return children == null ? Stream.empty() : Arrays.stream(children);
8280
}
8381
}

src/main/java/org/gradlex/javamodule/dependencies/internal/utils/ModuleInfoCache.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ public File getFolder(SourceSet sourceSet, ProviderFactory providers) {
8181
* @param projectRoot the project that should hold a Java module
8282
* @return parsed module-info.java for the given project assuming a standard Java project layout
8383
*/
84-
public ModuleInfo put(File projectRoot, String moduleInfoPath, String artifact, Provider<String> group, ProviderFactory providers) {
84+
public ModuleInfo put(File projectRoot, String moduleInfoPath, String projectPath, String artifact, Provider<String> group, ProviderFactory providers) {
8585
File folder = new File(projectRoot, moduleInfoPath);
8686
if (maybePutModuleInfo(folder, providers)) {
8787
ModuleInfo thisModuleInfo = moduleInfo.get(folder);
88-
moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), ":" + artifact);
88+
moduleNameToProjectPath.put(thisModuleInfo.getModuleName(), projectPath);
8989
Path parentDirectory = Paths.get(moduleInfoPath).getParent();
9090
String capabilitySuffix = parentDirectory == null ? null : sourceSetToCapabilitySuffix(parentDirectory.getFileName().toString());
9191
if (capabilitySuffix != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package org.gradlex.javamodule.dependencies.test.initialization
2+
3+
import org.gradlex.javamodule.dependencies.test.fixture.GradleBuild
4+
import spock.lang.Specification
5+
6+
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
7+
8+
class SettingsPluginIncludeTest extends Specification {
9+
10+
@Delegate
11+
GradleBuild build = new GradleBuild()
12+
13+
def setup() {
14+
settingsFile.text = '''
15+
plugins { id("org.gradlex.java-module-dependencies") }
16+
'''
17+
appBuildFile.delete()
18+
libBuildFile.delete()
19+
}
20+
21+
def "can define included subprojects as modules"() {
22+
given:
23+
settingsFile << '''
24+
include(":project:with:custom:path")
25+
javaModules {
26+
module(project(":project:with:custom:path")) {
27+
group = "org.example"
28+
plugin("java-library")
29+
}
30+
module(project(":project:with:custom")) {
31+
group = "org.example"
32+
plugin("java-library")
33+
}
34+
}
35+
'''
36+
37+
file('project/with/custom/path/src/main/java/module-info.java') << 'module abc.liba { }'
38+
file('project/with/custom/src/main/java/module-info.java') << '''module abc.libb {
39+
requires abc.liba;
40+
}'''
41+
42+
when:
43+
def result = runner(':project:with:custom:compileJava').build()
44+
45+
then:
46+
result.task(":project:with:custom:path:compileJava").outcome == SUCCESS
47+
result.task(":project:with:custom:compileJava").outcome == SUCCESS
48+
}
49+
50+
def "can define included subprojects with custom project directory as modules"() {
51+
given:
52+
settingsFile << '''
53+
include(":project:with:custom:path")
54+
project(":project:with:custom:path").projectDir = file("lib")
55+
project(":project:with:custom").projectDir = file("app")
56+
javaModules {
57+
module(project(":project:with:custom:path")) {
58+
group = "org.example"
59+
plugin("java-library")
60+
}
61+
module(project(":project:with:custom")) {
62+
group = "org.example"
63+
plugin("java-library")
64+
}
65+
}
66+
'''
67+
68+
file("project/with").mkdirs()
69+
libModuleInfoFile << 'module abc.lib { }'
70+
appModuleInfoFile << '''module abc.app {
71+
requires abc.lib;
72+
}'''
73+
74+
when:
75+
def result = runner(':project:with:custom:jar').build()
76+
77+
then:
78+
result.task(":project:with:custom:path:compileJava").outcome == SUCCESS
79+
result.task(":project:with:custom:compileJava").outcome == SUCCESS
80+
file("lib/build/libs/path.jar").exists()
81+
file("app/build/libs/custom.jar").exists()
82+
}
83+
84+
def "projects with same name but different paths are supported"() {
85+
given:
86+
settingsFile << '''
87+
include(":app1:feature1:data")
88+
include(":app1:feature2:data")
89+
90+
rootProject.children.forEach { appContainer ->
91+
appContainer.children.forEach { featureContainer ->
92+
featureContainer.children.forEach { module ->
93+
javaModules.module(module) { plugin("java-library") }
94+
}
95+
}
96+
}
97+
'''
98+
99+
file('app1/feature1/data/src/main/java/module-info.java') << 'module f1x.data { }'
100+
file('app1/feature2/data/src/main/java/module-info.java') << '''module f2x.data {
101+
requires f1x.data;
102+
}'''
103+
104+
when:
105+
def result = runner(':app1:feature2:data:jar').build()
106+
107+
then:
108+
result.task(":app1:feature1:data:jar").outcome == SUCCESS
109+
result.task(":app1:feature2:data:jar").outcome == SUCCESS
110+
file("app1/feature1/data/build/libs/data.jar").exists()
111+
file("app1/feature2/data/build/libs/data.jar").exists()
112+
}
113+
}

0 commit comments

Comments
 (0)