Skip to content

Commit

Permalink
Support using Gradle's 'include' in settings plugin (#143)
Browse files Browse the repository at this point in the history
This allows to configure project paths individually via the
native include("...") statement while still using other functionality
provided by the settings plugin.
  • Loading branch information
jjohannes authored Nov 4, 2024
1 parent a870abc commit 4315904
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
13 changes: 13 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -80,8 +79,7 @@ public void module(String subDirectory, Action<Module> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Module> 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<Module> 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);
}

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

for (Module module : moduleDirectory.customizedModules.values()) {
includeModule(module, new File(modulesDirectory, module.getDirectory().get()));
includeModule(module, module.directory);
}
Provider<List<String>> listProvider = getProviders().of(ValueModuleDirectoryListing.class, spec -> {
spec.getParameters().getExclusions().set(moduleDirectory.getExclusions());
Expand All @@ -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) {
Expand All @@ -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<String> 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<String> plugins;
private final String mainModuleName;

public ModuleProject(String artifact, String group, List<String> plugins, String mainModuleName) {
this.artifact = artifact;
public ModuleProject(String path, String group, List<String> plugins, String mainModuleName) {
this.path = path;
this.group = group;
this.plugins = plugins;
this.mainModuleName = mainModuleName;
Expand All @@ -170,7 +191,7 @@ public ApplyPluginsAction(List<ModuleProject> 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);
Expand All @@ -187,15 +208,15 @@ public void execute(Project project) {
@NonNullApi
private static class ApplyJavaModuleVersionsPluginAction implements IsolatedAction<Project> {

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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.
Expand All @@ -58,14 +53,17 @@ public abstract class Module {
*/
public abstract ListProperty<String> 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()));
}

/**
Expand All @@ -76,8 +74,8 @@ public void plugin(String id) {
getPlugins().add(id);
}

private Stream<File> listChildren(File root, String projectDir) {
File[] children = new File(root, projectDir).listFiles();
private Stream<File> listSrcChildren() {
File[] children = new File(directory, "src").listFiles();
return children == null ? Stream.empty() : Arrays.stream(children);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> group, ProviderFactory providers) {
public ModuleInfo put(File projectRoot, String moduleInfoPath, String projectPath, String artifact, Provider<String> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}

0 comments on commit 4315904

Please sign in to comment.