Skip to content

Commit

Permalink
#72: support for "modularity" project extension
Browse files Browse the repository at this point in the history
via ModularityExtension interface
  • Loading branch information
tlinkowski authored and paulbakker committed Apr 4, 2019
1 parent 150179c commit d49e06b
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 0 deletions.
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,13 +495,72 @@ patchModules.config = [
Compilation
===

Compilation to a specific Java release
----

You might want to run your builds on a recent JDK (e.g. JDK 12), but target an older version of Java, e.g.:
- Java 11, which is the current [Long-Term Support (LTS) release](https://www.oracle.com/technetwork/java/java-se-support-roadmap.html),
- Java 8, whose production use in 2018 was almost 85%, according to [this survey](https://www.baeldung.com/java-in-2018).

You can do that by setting the Java compiler [`--release`][javacRelease] option
(e.g. to `6` for Java 6, etc.). Note that when you build using:
- JDK 11: you can only target Java 6-11 using its
[`--release`](https://docs.oracle.com/en/java/javase/11/tools/javac.html) option,
- JDK 12: you can only target Java 7-12 using its
[`--release`](https://docs.oracle.com/en/java/javase/12/tools/javac.html) option,
- etc.

Finally, note that JPMS was introduced in Java 9, so you can't compile `module-info.java` to Java release 6-8
(this plugin provides a workaround for that, though — see below).

Concluding, to configure your project to support JPMS and target:
- Java **6-8**: call the [`modularity.mixedJavaRelease`][ModularityExtension] function
(see [Separate compilation of `module-info.java`](#separate-compilation-of-module-infojava) for details),
- Java **9+**: call the [`modularity.standardJavaRelease`][ModularityExtension] function,

and the plugin will take care of setting the [`--release`][javacRelease] option(s) appropriately.


Separate compilation of `module-info.java`
----

If you need to compile the main `module-info.java` separately from the rest of `src/main/java`
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 introducing split packages).

This plugin provides an easy way to do just that by means of its
[`modularity.mixedJavaRelease`][ModularityExtension] function, which implicitly sets
`compileJava.compileModuleInfoSeparately = true` and configures the [`--release`][javacRelease] compiler options.

For example, if your library targets JDK 8, and you want your `module-info.class` to target JDK 9
(default), put the following line in your `build.gradle(.kts)`:

<details open>
<summary>Groovy DSL</summary>

```groovy
modularity.mixedJavaRelease 8
```

</details>
<details>
<summary>Kotlin DSL</summary>

```kotlin
modularity.mixedJavaRelease(8)
```

</details>

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
===

Expand All @@ -524,3 +583,7 @@ 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.


[javacRelease]: http://openjdk.java.net/jeps/247
[ModularityExtension]: src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.DefaultModularityExtension;
import org.javamodularity.moduleplugin.extensions.ModularityExtension;
import org.javamodularity.moduleplugin.tasks.*;

public class ModuleSystemPlugin implements Plugin<Project> {
Expand All @@ -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", DefaultModularityExtension.class, project);

new CompileTask(project).configureCompileJava();
new CompileModuleInfoTask(project).configureCompileModuleInfoJava();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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 DefaultModularityExtension implements ModularityExtension {

private final Project project;

public DefaultModularityExtension(Project project) {
this.project = project;
}

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

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

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

private JavaProjectHelper helper() {
return new JavaProjectHelper(project);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.javamodularity.moduleplugin.extensions;

/**
* A project-wide extension that provides the most common modularity-related actions.
*
* @see DefaultModularityExtension
*/
public interface ModularityExtension {

/**
* Calling this method results in all Java classes being compiled to Java release 9+ (as given by the
* {@code mainJavaRelease} parameter).
* <p>
* See details about the {@code --release} option
* <a href="https://docs.oracle.com/en/java/javase/11/tools/javac.html">here</a>.
*
* @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).
* <p>
* See details about the {@code --release} option
* <a href="https://docs.oracle.com/en/java/javase/11/tools/javac.html">here</a>.
*
* @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);
}
3 changes: 3 additions & 0 deletions test-project-kotlin/greeter.api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ tasks {
}
}
}

modularity {
}
//endregion

val compileKotlin: KotlinCompile by tasks
Expand Down
3 changes: 3 additions & 0 deletions test-project/greeter.api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ test {
runOnClasspath = false
}
}

modularity {
}
//endregion

0 comments on commit d49e06b

Please sign in to comment.