From 99cc0b385c563ac6d4377ee0574bb72895db69f2 Mon Sep 17 00:00:00 2001 From: Sam Carlberg Date: Fri, 18 Jul 2025 22:09:49 -0400 Subject: [PATCH 1/4] Add all discovered modules from the classpath to compile tasks Allows user programs to use `import module` statements without needing to write `module-info,java` files (and keep them up to date as they add vendor libraries) --- .../first/gradlerio/wpi/WPIModulesPlugin.java | 43 +++++++++++++++++++ .../wpi/first/gradlerio/wpi/WPIPlugin.java | 1 + 2 files changed, 44 insertions(+) create mode 100644 src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java diff --git a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java new file mode 100644 index 00000000..3c2d839b --- /dev/null +++ b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java @@ -0,0 +1,43 @@ +package edu.wpi.first.gradlerio.wpi; + +import java.io.File; +import java.lang.module.ModuleFinder; +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.stream.Collectors; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.compile.JavaCompile; + +/** + * Configures java compilation tasks to automatically make available every module detected on the classpath. + */ +public class WPIModulesPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getTasks().withType(JavaCompile.class).forEach(compileJava -> { + compileJava.doFirst((task) -> { + var moduleFinder = + ModuleFinder.of(compileJava.getClasspath().getFiles().stream().map(File::toPath).toArray(Path[]::new)); + var moduleNames = + moduleFinder.findAll().stream().map(mod -> mod.descriptor().name()).collect(Collectors.joining(",")); + + var compilerArgs = new ArrayList<>(compileJava.getOptions().getCompilerArgs()); + compilerArgs.add("--module-path"); + compilerArgs.add(compileJava.getClasspath().getAsPath()); + + compilerArgs.add("--add-modules"); + compilerArgs.add(moduleNames); + + project.getLogger().debug("Adding modules to the compile task `{}`:", compileJava.getName()); + moduleFinder.findAll().stream().sorted(Comparator.comparing(mod -> mod.descriptor().name())).forEach(mod -> { + project.getLogger().debug("Adding module {} from {}", mod.descriptor().name(), mod.location().map(URI::toString).orElse("")); + }); + + compileJava.getOptions().setCompilerArgs(compilerArgs); + }); + }); + } +} diff --git a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIPlugin.java b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIPlugin.java index 2e988686..9563158f 100644 --- a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIPlugin.java +++ b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIPlugin.java @@ -40,6 +40,7 @@ public void apply(Project project) { project.getPluginManager().apply(WPIToolsPlugin.class); project.getPluginManager().apply(WPIDependenciesPlugin.class); + project.getPluginManager().apply(WPIModulesPlugin.class); project.getTasks().register("wpiVersions", task -> { task.setGroup("GradleRIO"); From d18d0f7b6f3c00d3c0697ee3cc7c8c5a76975601 Mon Sep 17 00:00:00 2001 From: Sam Carlberg Date: Fri, 18 Jul 2025 22:17:13 -0400 Subject: [PATCH 2/4] Add documentation --- .../wpi/first/gradlerio/wpi/WPIModulesPlugin.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java index 3c2d839b..90b7497d 100644 --- a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java +++ b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java @@ -19,6 +19,18 @@ public class WPIModulesPlugin implements Plugin { public void apply(Project project) { project.getTasks().withType(JavaCompile.class).forEach(compileJava -> { compileJava.doFirst((task) -> { + // Find all modular JAR files on the classpath, extract their module names (which may be different from + // the names of the containing JARs!), and pass those module names to the compile task using the --add-modules + // command line flag. + // + // --module-path is added as well so the compiler knows where to find those modules + // + // This is compatible with the modularity.inferModulePath setting that users can apply themselves (and which + // defaults to `true` in modern versions of Gradle), and is also compatible with user programs that manually + // specify a module-info.java file. We primarily expect our users not to write a module-info file, though, + // but still want to allow them to use `import module` statements. Thus, this plugin. + // + // See: https://dev.java/learn/modules/add-modules-reads/ var moduleFinder = ModuleFinder.of(compileJava.getClasspath().getFiles().stream().map(File::toPath).toArray(Path[]::new)); var moduleNames = From a9afacb63af5926ed122d62e00036e63dcc46c79 Mon Sep 17 00:00:00 2001 From: Sam Carlberg Date: Fri, 18 Jul 2025 22:19:45 -0400 Subject: [PATCH 3/4] Move debug statements before modifications to the compiler args --- .../edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java index 90b7497d..b793c69c 100644 --- a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java +++ b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java @@ -36,6 +36,11 @@ public void apply(Project project) { var moduleNames = moduleFinder.findAll().stream().map(mod -> mod.descriptor().name()).collect(Collectors.joining(",")); + project.getLogger().debug("Adding modules to the compile task `{}`:", compileJava.getName()); + moduleFinder.findAll().stream().sorted(Comparator.comparing(mod -> mod.descriptor().name())).forEach(mod -> { + project.getLogger().debug("Adding module {} from {}", mod.descriptor().name(), mod.location().map(URI::toString).orElse("")); + }); + var compilerArgs = new ArrayList<>(compileJava.getOptions().getCompilerArgs()); compilerArgs.add("--module-path"); compilerArgs.add(compileJava.getClasspath().getAsPath()); @@ -43,11 +48,6 @@ public void apply(Project project) { compilerArgs.add("--add-modules"); compilerArgs.add(moduleNames); - project.getLogger().debug("Adding modules to the compile task `{}`:", compileJava.getName()); - moduleFinder.findAll().stream().sorted(Comparator.comparing(mod -> mod.descriptor().name())).forEach(mod -> { - project.getLogger().debug("Adding module {} from {}", mod.descriptor().name(), mod.location().map(URI::toString).orElse("")); - }); - compileJava.getOptions().setCompilerArgs(compilerArgs); }); }); From b1473d1f41e3523d0c65b3123236ae8bf8309454 Mon Sep 17 00:00:00 2001 From: Sam Carlberg Date: Sun, 5 Oct 2025 19:50:19 -0400 Subject: [PATCH 4/4] Exclude automatic modules --- .../first/gradlerio/wpi/WPIModulesPlugin.java | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java index b793c69c..9c7c439e 100644 --- a/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java +++ b/src/main/java/edu/wpi/first/gradlerio/wpi/WPIModulesPlugin.java @@ -4,21 +4,22 @@ import java.lang.module.ModuleFinder; import java.net.URI; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import java.util.stream.Collectors; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.tasks.compile.JavaCompile; /** - * Configures java compilation tasks to automatically make available every module detected on the classpath. + * Configures java compilation tasks to automatically make available every module detected on the classpath. Automatic + * modules will not be included. */ public class WPIModulesPlugin implements Plugin { @Override public void apply(Project project) { project.getTasks().withType(JavaCompile.class).forEach(compileJava -> { - compileJava.doFirst((task) -> { + compileJava.doFirst(task -> { // Find all modular JAR files on the classpath, extract their module names (which may be different from // the names of the containing JARs!), and pass those module names to the compile task using the --add-modules // command line flag. @@ -31,24 +32,39 @@ public void apply(Project project) { // but still want to allow them to use `import module` statements. Thus, this plugin. // // See: https://dev.java/learn/modules/add-modules-reads/ + var classpathJars = + compileJava.getClasspath().getFiles().stream() + .map(File::toPath) + .filter(p -> p.getFileName().toString().endsWith(".jar")) + .toList(); + var moduleFinder = - ModuleFinder.of(compileJava.getClasspath().getFiles().stream().map(File::toPath).toArray(Path[]::new)); - var moduleNames = - moduleFinder.findAll().stream().map(mod -> mod.descriptor().name()).collect(Collectors.joining(",")); + ModuleFinder.of(classpathJars.toArray(Path[]::new)); + + // Select only explicit modules. Automatic modules may not actually be compatible with the JPMS; notably, + // EJML splits packages across multiple JARs, which is not permitted by the module system. + var modules = moduleFinder.findAll().stream() + .filter(mod -> !mod.descriptor().isAutomatic()) + .sorted(Comparator.comparing(mod -> mod.descriptor().name())) + .toList(); project.getLogger().debug("Adding modules to the compile task `{}`:", compileJava.getName()); - moduleFinder.findAll().stream().sorted(Comparator.comparing(mod -> mod.descriptor().name())).forEach(mod -> { - project.getLogger().debug("Adding module {} from {}", mod.descriptor().name(), mod.location().map(URI::toString).orElse("")); + modules.forEach(mod -> { + project.getLogger().debug( + "Adding module {} from {}", + mod.descriptor().name(), + mod.location().map(URI::toString).orElse("")); }); - var compilerArgs = new ArrayList<>(compileJava.getOptions().getCompilerArgs()); - compilerArgs.add("--module-path"); - compilerArgs.add(compileJava.getClasspath().getAsPath()); - - compilerArgs.add("--add-modules"); - compilerArgs.add(moduleNames); + var moduleNames = + modules.stream().map(mod -> mod.descriptor().name()).collect(Collectors.joining(",")); - compileJava.getOptions().setCompilerArgs(compilerArgs); + compileJava.getOptions().getCompilerArgs().addAll(List.of( + "--module-path", + compileJava.getClasspath().getAsPath(), + "--add-modules", + moduleNames + )); }); }); }