diff --git a/README.MD b/README.MD
index 4fc55bb..7cda868 100644
--- a/README.MD
+++ b/README.MD
@@ -3,11 +3,15 @@
[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fgradlex-org%2Fjava-module-dependencies%2Fbadge%3Fref%3Dmain&style=flat)](https://actions-badge.atrox.dev/gradlex-org/java-module-dependencies/goto?ref=main)
[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v?label=Plugin%20Portal&metadataUrl=https%3A%2F%2Fplugins.gradle.org%2Fm2%2Forg%2Fgradlex%2Fjava-module-dependencies%2Forg.gradlex.java-module-dependencies.gradle.plugin%2Fmaven-metadata.xml)](https://plugins.gradle.org/plugin/org.gradlex.java-module-dependencies)
-A Gradle 7.4+ plugin to make Gradle use dependencies from _module-info.java_ files automatically.
+A Gradle plugin to make Gradle use dependencies from _module-info.java_ files automatically.
If you have a project that fully uses Java Modules, you do **not** need to declare dependencies in the `dependencies { }` block anymore.
Gradle will use the information from your `module-info.java` directly.
-To manage the versions of Java Modules, the plugin conveniently integrates with
+Minimal required Gradle version:
+- **Gradle 7.4** if you **not** use the plugin in `settings.gradle.kts`
+- **Gradle 8.8** to use the plugin in `settings.gradle.kts` and the [additional functionality](#project-structure-definition-when-using-this-plugin-as-settings-plugin) that comes with it.
+
+To manage the versions of Java Modules, the plugin integrates with
[Platform Projects](https://docs.gradle.org/current/userguide/java_platform_plugin.html#sec:java_platform_usage) and
[Dependency Versions Constraints](https://docs.gradle.org/current/userguide/dependency_constraints.html#sec:adding-constraints-transitive-deps) in general
as well as [Version Catalogs](https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog).
@@ -22,36 +26,28 @@ There is a [CHANGELOG.md](CHANGELOG.md).
# Java Modules with Gradle
-If you plan to build Java Modules with Gradle, you should consider using these plugins on top of Gradle core:
+If you build Java Modules with Gradle, you should consider using these plugins on top of Gradle core:
-- [`id("org.gradlex.java-module-dependencies")`](https://github.com/gradlex-org/java-module-dependencies)
+- [`id("org.gradlex.java-module-dependencies")`](https://github.com/gradlex-org/java-module-dependencies) (this plugin)
Avoid duplicated dependency definitions and get your Module Path under control
- [`id("org.gradlex.java-module-testing")`](https://github.com/gradlex-org/java-module-testing)
Proper test setup for Java Modules
- [`id("org.gradlex.extra-java-module-info")`](https://github.com/gradlex-org/extra-java-module-info)
- Only if your (existing) project cannot avoid using non-module legacy Jars
-
-[Here is a sample](https://github.com/gradlex-org/java-module-testing/tree/main/samples/use-all-java-module-plugins)
-that shows all plugins in combination.
+ Only if you cannot avoid using non-module legacy Jars
[In episodes 31, 32, 33 of Understanding Gradle](https://github.com/jjohannes/understanding-gradle) I explain what these plugins do and why they are needed.
[](https://www.youtube.com/watch?v=X9u1taDwLSA&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE)
[](https://www.youtube.com/watch?v=T9U0BOlVc-c&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE)
[](https://www.youtube.com/watch?v=6rFEDcP8Noc&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE)
-[Full Java Module System Project Setup](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_module_system) is a full-fledged Java Module System project setup using these plugins.
-[](https://www.youtube.com/watch?v=uRieSnovlVc&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE)
-
# How to use?
-For a quick start, you can find some samples here:
-* [samples/versions-in-platform](samples/versions-in-platform)
-* [samples/versions-in-catalog](samples/versions-in-catalog)
-* [samples/module-info-dsl](samples/module-info-dsl)
-* [samples/module-info-dsl-no-platform](samples/module-info-dsl-no-platform)
-* [samples/kotlin](samples/kotlin)
+Working (example) projects to inspect:
+- [java-module-system](https://github.com/jjohannes/java-module-system) contains a compact sample and further documentation
+- [gradle-project-setup-howto](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_module_system) is a full-fledged Java Module System project setup
+- [hedera-services](https://github.com/hashgraph/hedera-services) is an open-source Java project using this plugin large scale
-For general information about how to structure Gradle builds and apply community plugins like this one to all subprojects
+For general information about how to structure Gradle builds and apply community plugins like this one
you can check out my [Understanding Gradle video series](https://www.youtube.com/playlist?list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE).
## Plugin dependency
@@ -61,7 +57,7 @@ Add this to the build file of your convention plugin's build
```
dependencies {
- implementation("org.gradlex:java-module-dependencies:1.6.6")
+ implementation("org.gradlex:java-module-dependencies:1.7")
}
```
@@ -72,12 +68,14 @@ dependencies {
## Apply the plugin
-In your convention plugin, apply the plugin.
+The plugin can be used in two ways:
+
+1. As _Settings Plugin_ in `settings.gradle(.kts)` file **(recommended)**
+2. As _Project Plugin_ in `build.gradle(.kts)` files (sometimes easier to add to existing setups)
```
plugins {
- ...
- id("org.gradlex.java-module-dependencies")
+ id("org.gradlex.java-module-dependencies")
}
```
@@ -87,15 +85,54 @@ Once the plugin is applied, dependencies are automatically determined based on t
```
module org.example.mymodule {
- requires com.fasterxml.jackson.core; // -> implementation("com.fasterxml.jackson.core:jackson-core")
- requires transitive org.slf4j; // -> api("org.slf4j:slf4j-api")
- requires static jakarta.servlet; // -> compileOnly("jakarta.servlet:jakarta.servlet-api")
- requires /*runtime*/ org.slf4j.simple; // -> runtimeOnly("org.slf4j:slf4j-simple")
+ requires com.fasterxml.jackson.core; // -> implementation("com.fasterxml.jackson.core:jackson-core")
+ requires transitive org.slf4j; // -> api("org.slf4j:slf4j-api")
+ requires static jakarta.servlet; // -> compileOnly("jakarta.servlet:jakarta.servlet-api")
+ requires /*runtime*/ org.slf4j.simple; // -> runtimeOnly("org.slf4j:slf4j-simple")
}
```
Note that `requires /*runtime*/` is a directive specifically supported by this plugin to allow the specification of _runtime only_ dependencies.
+How the plugin finds the `module-info.java` files and establishes relationships between them
+
+## Project structure definition when using this plugin as Settings Plugin
+
+The plugin offers a Gradle DSL extension to configure the location of _Java Modules_ in the project structure to be used
+in the `settings.gradle(.kts)` file. It is an alternative to Gradle's native `include(...)` statement to configure
+subprojects. The advantage of using this is that it is more compact than Gradle's `include(...)` and allows the plugin
+to pick up more information during the [initialization phase](https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:initialization)
+By this, the plugin is later able to establish dependencies between your own modules without making assumptions about
+how they need to be named (which is different when you use the plugin as
+[Project Plugin](#project-structure-definition-when-using-this-plugin-as-project-plugin)).
+
+```
+// settings.gradle(.kts)
+javaModules { // use instead of 'include(...)'
+ module("module-a") // Module in directory 'module-a' - searches 'src/*/java' for 'module-info.java' files
+
+ module("module-b") {
+ group = "org.example" // define group early in settings so that all projects know all groups
+ artifact = "lib-x" // Gradle subproject name (if differnt than directory)
+ plugin("java-library") // apply plugin to the Module's subproject to omit 'build.gradle' completey
+ }
+
+ directory("modules") { // Auto-include all Modules in subfolders of 'modules'
+ group = "org.example" // group for all Modules
+ plugin("java-library") // apply plugin to all Modules' subprojects
+ module("app") { ... } // individualise Module (only if needed)
+ }
+}
+```
+
+## Project structure definition when using this plugin as Project Plugin
+
+In this setup, subproject with Java Module are configured as in any traditional Gradle build: by using the
+`include(...)` statement in `settings.gradle(.kts)`. The plugin is then applied in all subprojects with Java Modules,
+ideally though a convention plugin. If you use the plugin like this, it needs to make some assumption due to missing
+information and thus, for example, requires you to have the Gradle _project names_, _groups_ and _Java Module Names_
+align. The preferred way is to use the plugin as [Settings Plugin](#project-structure-definition-when-using-this-plugin-as-settings-plugin).
+
## Define additional module dependencies in build files
With this plugin you move dependency definitions into `module-info.java` files and no longer use the `dependencies {}` block in build files.
@@ -104,8 +141,8 @@ For this, the plugin offers an extension of Gradle's DSL to be used in `build.gr
```
mainModuleInfo {
- runtimeOnly("org.slf4j.simple") // runtime only dependency for the 'main' module
- annotationProcessor("dagger.compiler") // annotation processor dependency for the 'main' module
+ runtimeOnly("org.slf4j.simple") // runtime only dependency for the 'main' module
+ annotationProcessor("dagger.compiler") // annotation processor dependency for the 'main' module
}
```
@@ -116,9 +153,9 @@ The only case where this should be used is for whitebox testing activated via th
```
testModuleInfo {
- requires("org.assertj.core")
- requires("org.hamcrest")
- requires("org.junit.jupiter.api")
+ requires("org.assertj.core")
+ requires("org.hamcrest")
+ requires("org.junit.jupiter.api")
}
```
@@ -142,11 +179,11 @@ org.apache.commons.lang3.test.fixtures=org.apache.commons:commons-lang3|test-fix
```
javaModuleDependencies {
- // Module Name to Component GA Coordinates
- moduleNameToGA.put("org.apache.commons.lang3", "org.apache.commons:commons-lang3")
+ // Module Name to Component GA Coordinates
+ moduleNameToGA.put("org.apache.commons.lang3", "org.apache.commons:commons-lang3")
- // Module Name to Component GA Coordinates & Capability GA Coordinates
- moduleNameToGA.put("org.apache.commons.lang3.test.fixtures", "org.apache.commons:commons-lang3|test-fixtures")
+ // Module Name to Component GA Coordinates & Capability GA Coordinates
+ moduleNameToGA.put("org.apache.commons.lang3.test.fixtures", "org.apache.commons:commons-lang3|test-fixtures")
}
```
@@ -177,22 +214,22 @@ For libraries that consist of multiple components and have a BOM for version man
```
plugins {
- id("java-platform")
- id("org.gradlex.java-module-versions")
+ id("java-platform")
+ id("org.gradlex.java-module-versions")
}
// Define versions for Modules via the Module Name
moduleInfo {
- version("org.apache.xmlbeans", "5.0.1")
- version("org.slf4j", "2.0.7")
- version("org.slf4j.simple", "2.0.7")
+ version("org.apache.xmlbeans", "5.0.1")
+ version("org.slf4j", "2.0.7")
+ version("org.slf4j.simple", "2.0.7")
}
// Use BOMs for Modules that are part of a library of multiple Modules
javaPlatform.allowDependencies()
dependencies {
- api(platform("com.fasterxml.jackson:jackson-bom:2.13.2"))
- api(platform("org.junit:junit-bom:5.8.2"))
+ api(platform("com.fasterxml.jackson:jackson-bom:2.13.2"))
+ api(platform("org.junit:junit-bom:5.8.2"))
}
```
@@ -201,9 +238,9 @@ For example:
```
dependencies {
- javaModuleDependencies {
- testRuntimeOnly(ga("org.junit.jupiter.engine"))
- }
+ javaModuleDependencies {
+ testRuntimeOnly(ga("org.junit.jupiter.engine"))
+ }
}
```
@@ -216,13 +253,13 @@ Alternatively, versions can be defined in the `[version]` block of a [version ca
```
dependencyResolutionManagement {
- versionCatalogs.create("libs") {
- version("org.apache.xmlbeans", "5.0.1")
- version("com.fasterxml.jackson.databind", "2.12.5")
- version("org.slf4j", "2.0.7")
-
- version("org.junit.jupiter.api", "5.8.2")
- }
+ versionCatalogs.create("libs") {
+ version("org.apache.xmlbeans", "5.0.1")
+ version("com.fasterxml.jackson.databind", "2.12.5")
+ version("org.slf4j", "2.0.7")
+
+ version("org.junit.jupiter.api", "5.8.2")
+ }
}
```
@@ -250,19 +287,19 @@ $ ./gradlew :app:recommendModuleVersions -q
Latest Stable Versions of Java Modules - use in your platform project's build.gradle(.kts)
==========================================================================================
moduleInfo {
- version("com.fasterxml.jackson.annotation", "2.13.2")
- version("com.fasterxml.jackson.core", "2.13.2")
- version("com.fasterxml.jackson.databind", "2.13.2.2")
- version("org.apache.logging.log4j", "2.17.2")
- version("org.apache.xmlbeans", "5.0.3")
- version("org.junit.jupiter.api", "5.8.2")
- version("org.junit.jupiter.engine", "5.8.2")
- version("org.junit.platform.commons", "1.8.2")
- version("org.junit.platform.engine", "1.8.2")
- version("org.junit.platform.launcher", "1.8.2")
- version("org.opentest4j", "1.2.0")
- version("org.slf4j", "1.7.36")
- version("org.slf4j.simple", "1.7.36")
+ version("com.fasterxml.jackson.annotation", "2.13.2")
+ version("com.fasterxml.jackson.core", "2.13.2")
+ version("com.fasterxml.jackson.databind", "2.13.2.2")
+ version("org.apache.logging.log4j", "2.17.2")
+ version("org.apache.xmlbeans", "5.0.3")
+ version("org.junit.jupiter.api", "5.8.2")
+ version("org.junit.jupiter.engine", "5.8.2")
+ version("org.junit.platform.commons", "1.8.2")
+ version("org.junit.platform.engine", "1.8.2")
+ version("org.junit.platform.launcher", "1.8.2")
+ version("org.opentest4j", "1.2.0")
+ version("org.slf4j", "1.7.36")
+ version("org.slf4j.simple", "1.7.36")
}
```
@@ -391,14 +428,14 @@ Module Name mappings for Jars that were patched with extra module info will be a
```
plugins {
- id("org.gradlex.extra-java-module-info")
- id("org.gradlex.java-module-dependencies")
+ id("org.gradlex.extra-java-module-info")
+ id("org.gradlex.java-module-dependencies")
}
extraJavaModuleInfo {
- automaticModule("org.apache.commons:commons-math3", "commons.math3")
- // Module Dependencies plugin automatically knows that
- // 'commons.math3' now maps to 'org.apache.commons:commons-math3'
+ automaticModule("org.apache.commons:commons-math3", "commons.math3")
+ // Module Dependencies plugin automatically knows that
+ // 'commons.math3' now maps to 'org.apache.commons:commons-math3'
}
```
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 9c0e20e..9160e6e 100644
--- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java
+++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Directory.java
@@ -31,25 +31,43 @@ public abstract class Directory {
private final File root;
final Map customizedModules = new LinkedHashMap<>();
+ /**
+ * {@link Module#getGroup()}
+ */
public abstract Property getGroup();
+
+ /**
+ * {@link Module#plugin(String)}
+ */
public abstract ListProperty getPlugins();
@Inject
- public abstract ObjectFactory getObjects();
+ protected abstract ObjectFactory getObjects();
@Inject
public Directory(File root) {
this.root = root;
}
+ /**
+ * {@link Module#plugin(String)}
+ */
public void plugin(String id) {
getPlugins().add(id);
}
+ /**
+ * {@link Directory#module(String, Action)}
+ */
public void module(String subDirectory) {
module(subDirectory, m -> {});
}
+ /**
+ * Configure details of a Module in a subdirectory of this directory.
+ * Note that Modules that are located in direct children of this directory are discovered automatically and
+ * do not need to be explicitly mentioned.
+ */
public void module(String subDirectory, Action action) {
Module module = addModule(subDirectory);
action.execute(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 1d51696..30ec4b7 100644
--- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java
+++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/JavaModulesExtension.java
@@ -26,7 +26,6 @@
import org.gradle.api.plugins.ApplicationPlugin;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.plugins.JavaPlatformPlugin;
-import org.gradle.util.GradleVersion;
import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension;
import org.gradlex.javamodule.dependencies.JavaModuleDependenciesPlugin;
import org.gradlex.javamodule.dependencies.JavaModuleVersionsPlugin;
@@ -38,7 +37,6 @@
import java.io.File;
import java.nio.file.Paths;
import java.util.List;
-import java.util.Map;
public abstract class JavaModulesExtension {
@@ -54,38 +52,54 @@ public JavaModulesExtension(Settings settings) {
this.moduleInfoCache = getObjects().newInstance(ModuleInfoCache.class, true);
}
- public void module(String path) {
- module(path, m -> {});
+ /**
+ * {@link JavaModulesExtension#module(String, Action)}
+ */
+ public void module(String directory) {
+ module(directory, m -> {});
}
- public void module(String path, Action action) {
+ /**
+ * 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(path);
+ module.getDirectory().set(directory);
action.execute(module);
includeModule(module, new File(settings.getRootDir(), module.getDirectory().get()));
}
- public void directory(String path) {
- directory(path, m -> {});
+ /**
+ * {@link JavaModulesExtension#directory(String, Action)}
+ */
+ public void directory(String directory) {
+ directory(directory, m -> {});
}
- public void directory(String path, Action action) {
- File modulesDirectory = new File(settings.getRootDir(), path);
- Directory directory = getObjects().newInstance(Directory.class, modulesDirectory);
- action.execute(directory);
+ /**
+ * Register and configure ALL Modules located in direct subfolders of the given folder.
+ */
+ public void directory(String directory, Action action) {
+ File modulesDirectory = new File(settings.getRootDir(), directory);
+ Directory moduleDirectory = getObjects().newInstance(Directory.class, modulesDirectory);
+ action.execute(moduleDirectory);
File[] projectDirs = modulesDirectory.listFiles();
if (projectDirs == null) {
throw new RuntimeException("Failed to inspect: " + modulesDirectory);
}
- for (Module module : directory.customizedModules.values()) {
+ for (Module module : moduleDirectory.customizedModules.values()) {
includeModule(module, new File(modulesDirectory, module.getDirectory().get()));
}
for (File projectDir : projectDirs) {
- if (!directory.customizedModules.containsKey(projectDir.getName())) {
- includeModule(directory.addModule(projectDir.getName()), projectDir);
+ if (!moduleDirectory.customizedModules.containsKey(projectDir.getName())) {
+ Module module = moduleDirectory.addModule(projectDir.getName());
+ if (!module.getModuleInfoPaths().get().isEmpty()) {
+ // only auto-include if there is at least one module-info.java
+ includeModule(module, projectDir);
+ }
}
}
}
@@ -98,18 +112,13 @@ public void versions(String directory) {
}
private void includeModule(Module module, File projectDir) {
- List modulePaths = module.getModuleInfoPaths().get();
- if (modulePaths.isEmpty()) {
- return;
- }
-
String artifact = module.getArtifact().get();
settings.include(artifact);
ProjectDescriptor project = settings.project(":" + artifact);
project.setProjectDir(projectDir);
String mainModuleName = null;
- for (String path : modulePaths) {
+ for (String path : module.getModuleInfoPaths().get()) {
ModuleInfo moduleInfo = moduleInfoCache.put(projectDir, path,
module.getArtifact().get(), module.getGroup(), settings.getProviders());
if (path.contains("/main/")) {
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 056afd2..5f98e7c 100644
--- a/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java
+++ b/src/main/java/org/gradlex/javamodule/dependencies/initialization/Module.java
@@ -27,10 +27,35 @@
import java.util.stream.Stream;
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.
+ */
public abstract Property getArtifact();
+
+ /**
+ * The 'group' of the Module. This corresponds to setting the 'group' property in a build.gradle file. If the
+ * Module is published to a Maven repository, this is the 'group' in the 'group:artifact' identifier to address
+ * the published Jar. The group needs to be configured here (rather than in build.gradle files) for the plugin
+ * to support additional Modules inside a subproject that other modules depend on, such as a 'testFixtures' module.
+ */
public abstract Property getGroup();
+
+ /**
+ * The paths of the module-info.java files inside the project directory. Usually, this does not need to be adjusted.
+ * By default, it contains all 'src/$sourceSetName/java/module-info.java' files that exist.
+ */
public abstract ListProperty getModuleInfoPaths();
+
+ /**
+ * {@link Module#plugin(String)}
+ */
public abstract ListProperty getPlugins();
@Inject
@@ -43,12 +68,16 @@ public Module(File root) {
.collect(Collectors.toList())));
}
+ /**
+ * Apply a plugin to the Module project. This is the same as using the 'plugins { }' block in the Module's
+ * build.gradle file. Applying plugins here allows you to omit build.gradle files completely.
+ */
+ public void plugin(String id) {
+ getPlugins().add(id);
+ }
+
private Stream listChildren(File root, String projectDir) {
File[] children = new File(root, projectDir).listFiles();
return children == null ? Stream.empty() : Arrays.stream(children);
}
-
- public void plugin(String id) {
- getPlugins().add(id);
- }
}