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); - } }