diff --git a/README.md b/README.md index 6b38beb..88eb0b8 100644 --- a/README.md +++ b/README.md @@ -448,6 +448,14 @@ patchModules.config = [ Compilation === +Compilation to a specific Java release +---- + +For Java releases 6-8, see [Separate compilation of `module-info.java`](#separate-compilation-of-module-infojava). + +For Java releases 9+, this plugin provides an easy way to set the `--release` option of the `compileJava` task +by means of its [`modularity.standardJavaRelease`][ModularityExtension] function. + Separate compilation of `module-info.java` ---- @@ -455,6 +463,39 @@ If you need to compile the main `module-info.java` separately from the rest of ` files, you can enable `compileModuleInfoSeparately` option on `compileJava` task. It will exclude `module-info.java` from `compileJava` and introduce a dedicated `compileModuleInfoJava` task. +Typically, this feature would be used by libraries which target JDK 6-8 but want to make the most of JPMS by: +- providing `module-info.class` for consumers who put the library on module path, +- compiling `module-info.java` against the remaining classes of this module and against other modules (which provides +better encapsulation and prevents split packages). + +This plugin provides an easy way to do exactly that by means of its +[`modularity.mixedJavaRelease`][ModularityExtension] function, which implicitly sets +`compileJava.compileModuleInfoSeparately` and configures `--release` compiler options. + +For example, if your library targets JDK 8, and you want your `module-info.class` to be compatible with JDK 9 +(which is the default), put the following line in your `build.gradle(.kts)`: + +
+Groovy DSL + +```groovy +modularity.mixedJavaRelease 8 +``` + +
+
+Kotlin DSL + +```kotlin +modularity.mixedJavaRelease(8) +``` + +
+ +Note that `modularity.mixedJavaRelease` does *not* configure a +[multi-release JAR](https://docs.oracle.com/javase/9/docs/specs/jar/jar.html#Multi-release) +(in other words, `module-info.class` remains in the root directory of the JAR). + Limitations === @@ -477,3 +518,6 @@ Contributions are very much welcome. Please open a Pull Request with your changes. Make sure to rebase before creating the PR so that the PR only contains your changes, this makes the review process much easier. Again, bonus points for providing tests for your changes. + + +[ModularityExtension]: src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java diff --git a/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java b/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java index 0fbbc08..bd8958e 100644 --- a/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java +++ b/src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java @@ -4,6 +4,8 @@ import org.gradle.api.Project; import org.gradle.api.plugins.ExtensionContainer; import org.gradle.api.plugins.JavaPlugin; +import org.javamodularity.moduleplugin.extensions.ModularityExtension; +import org.javamodularity.moduleplugin.extensions.ModularityExtensionImpl; import org.javamodularity.moduleplugin.tasks.*; public class ModuleSystemPlugin implements Plugin { @@ -18,6 +20,7 @@ private void configureModularity(Project project, String moduleName) { ExtensionContainer extensions = project.getExtensions(); extensions.add("moduleName", moduleName); extensions.create("patchModules", PatchModuleExtension.class); + extensions.create(ModularityExtension.class, "modularity", ModularityExtensionImpl.class, project); new CompileTask(project).configureCompileJava(); new CompileModuleInfoTask(project).configureCompileModuleInfoJava(); diff --git a/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java b/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java new file mode 100644 index 0000000..910d797 --- /dev/null +++ b/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java @@ -0,0 +1,49 @@ +package org.javamodularity.moduleplugin.extensions; + +/** + * A project-wide extension that provides the most common modularity-related actions. + * + * @see ModularityExtensionImpl + */ +public interface ModularityExtension { + + //region JAVA RELEASE + + /** + * Calling this method results in all Java classes being compiled to Java release 9+ (as given by the + * {@code mainJavaRelease} parameter). + *

+ * See details about the {@code --release} option + * here. + * + * @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task (allowed range: 9+) + */ + void standardJavaRelease(int mainJavaRelease); + + /** + * Calling this method results in all Java classes being compiled to Java release 6-8 (as given by the + * {@code mainJavaRelease} parameter), with the exception of {@code module-info.java} being compiled to + * Java release 9. + * + * @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task (allowed range: 6-8) + */ + default void mixedJavaRelease(int mainJavaRelease) { + mixedJavaRelease(mainJavaRelease, 9); + } + + /** + * Calling this method results in all Java classes being compiled to Java release 6-8 (as given by the + * {@code mainJavaRelease} parameter), with the exception of {@code module-info.java} being compiled to + * Java release 9+ (as given by the {@code moduleInfoJavaRelease} parameter). + *

+ * See details about the {@code --release} option + * here. + * + * @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task + * (allowed range: 6-8) + * @param moduleInfoJavaRelease value for the {@code --release} option of {@code compileModuleInfoJava} task + * (allowed range: 9+) + */ + void mixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease); + //endregion +} diff --git a/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtensionImpl.java b/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtensionImpl.java new file mode 100644 index 0000000..967ac82 --- /dev/null +++ b/src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtensionImpl.java @@ -0,0 +1,97 @@ +package org.javamodularity.moduleplugin.extensions; + +import org.gradle.api.JavaVersion; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.compile.JavaCompile; +import org.javamodularity.moduleplugin.JavaProjectHelper; + +import java.util.List; + +public class ModularityExtensionImpl implements ModularityExtension { + + private final Project project; + + public ModularityExtensionImpl(Project project) { + this.project = project; + } + + //region JAVA RELEASE + + //region STANDARD JAVA RELEASE + @Override + public void standardJavaRelease(int mainJavaRelease) { + if (mainJavaRelease < 9) { + throw new IllegalArgumentException(String.format( + "Invalid main --release value: %d. Use 'mixedJavaRelease' instead.", mainJavaRelease + )); + } + project.afterEvaluate(p -> configureStandardJavaRelease(mainJavaRelease)); + } + + private void configureStandardJavaRelease(int mainJavaRelease) { + JavaCompile compileJava = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME); + setJavaRelease(compileJava, mainJavaRelease); + } + //endregion + + //region MIXED JAVA RELEASE + @Override + public void mixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease) { + validateMixedJavaReleaseArgs(mainJavaRelease, moduleInfoJavaRelease); + + CompileModuleOptions moduleOptions = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME) + .getExtensions().getByType(CompileModuleOptions.class); + moduleOptions.setCompileModuleInfoSeparately(true); + + project.afterEvaluate(p -> configureMixedJavaRelease(mainJavaRelease, moduleInfoJavaRelease)); + } + + private static void validateMixedJavaReleaseArgs(int mainJavaRelease, int moduleInfoJavaRelease) { + if (mainJavaRelease < 6) { + throw new IllegalArgumentException("Invalid main --release value: " + mainJavaRelease); + } + if (mainJavaRelease > 8) { + throw new IllegalArgumentException(String.format( + "Invalid main --release value: %d. Use 'standardJavaRelease' instead.", mainJavaRelease + )); + } + if (moduleInfoJavaRelease < 9) { + throw new IllegalArgumentException("Invalid module-info --release value: " + moduleInfoJavaRelease); + } + } + + private void configureMixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease) { + var compileJava = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME); + setJavaRelease(compileJava, mainJavaRelease); + + var compileModuleInfoJava = helper().compileJavaTask(CompileModuleOptions.COMPILE_MODULE_INFO_TASK_NAME); + setJavaRelease(compileModuleInfoJava, moduleInfoJavaRelease); + } + //endregion + + // TODO: Remove this method when Gradle supports it natively: https://github.com/gradle/gradle/issues/2510 + private void setJavaRelease(JavaCompile javaCompile, int javaRelease) { + String currentJavaVersion = JavaVersion.current().toString(); + if (!javaCompile.getSourceCompatibility().equals(currentJavaVersion)) { + throw new IllegalStateException("sourceCompatibility should not be set together with --release option"); + } + if (!javaCompile.getTargetCompatibility().equals(currentJavaVersion)) { + throw new IllegalStateException("targetCompatibility should not be set together with --release option"); + } + + List compilerArgs = javaCompile.getOptions().getCompilerArgs(); + if (compilerArgs.contains("--release")) { + throw new IllegalStateException("--release option is already set in compiler args"); + } + + compilerArgs.add("--release"); + compilerArgs.add(String.valueOf(javaRelease)); + } + //endregion + + private JavaProjectHelper helper() { + return new JavaProjectHelper(project); + } + +} diff --git a/test-project-kotlin/greeter.api/build.gradle.kts b/test-project-kotlin/greeter.api/build.gradle.kts index 566106a..d0dcd7b 100644 --- a/test-project-kotlin/greeter.api/build.gradle.kts +++ b/test-project-kotlin/greeter.api/build.gradle.kts @@ -18,6 +18,9 @@ tasks { } } } + +modularity { +} //endregion val compileKotlin: KotlinCompile by tasks diff --git a/test-project/greeter.api/build.gradle b/test-project/greeter.api/build.gradle index c676ee8..ae58af7 100644 --- a/test-project/greeter.api/build.gradle +++ b/test-project/greeter.api/build.gradle @@ -16,4 +16,7 @@ test { runOnClasspath = false } } + +modularity { +} //endregion