Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jjohannes committed Jul 16, 2024
1 parent cbacb93 commit 2ff9b88
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 41 deletions.
70 changes: 55 additions & 15 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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 plugins in `settings.gradle.kts`
- **Gradle 8.8** to use the plugin in `settings.gradle.kts` and the additional [lalala](lalala) functionality 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).
Expand Down Expand Up @@ -39,19 +43,14 @@ that shows all plugins in combination.
[<img src="https://onepiecesoftware.github.io/img/videos/32.png" width="260">](https://www.youtube.com/watch?v=T9U0BOlVc-c&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE)
[<img src="https://onepiecesoftware.github.io/img/videos/33.png" width="260">](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.
[<img src="https://onepiecesoftware.github.io/img/videos/15-3.png" width="260">](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) repository contains a compact sample fully using these plugins and pointers to further documentation
- [gradle-project-setup-howto](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_module_system) repository is a full-fledged Java Module System project setup using these plugins.
- [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
Expand All @@ -61,7 +60,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")
}
```

Expand All @@ -72,11 +71,13 @@ 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 (maybe easier to add to existing setups)

```
plugins {
...
id("org.gradlex.java-module-dependencies")
}
```
Expand All @@ -96,6 +97,45 @@ module org.example.mymodule {

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,43 @@ public abstract class Directory {
private final File root;
final Map<String, Module> customizedModules = new LinkedHashMap<>();

/**
* {@link Module#getGroup()}
*/
public abstract Property<String> getGroup();

/**
* {@link Module#plugin(String)}
*/
public abstract ListProperty<String> 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<Module> action) {
Module module = addModule(subDirectory);
action.execute(module);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand All @@ -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<Module> action) {
/**
* 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(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<Directory> 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<Directory> 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);
}
}
}
}
Expand All @@ -98,18 +112,13 @@ public void versions(String directory) {
}

private void includeModule(Module module, File projectDir) {
List<String> 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/")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<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.
*/
public abstract Property<String> 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<String> 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<String> getModuleInfoPaths();

/**
* {@link Module#plugin(String)}
*/
public abstract ListProperty<String> getPlugins();

@Inject
Expand All @@ -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<File> 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);
}
}

0 comments on commit 2ff9b88

Please sign in to comment.