Skip to content

Commit d49e06b

Browse files
tlinkowskipaulbakker
authored andcommittedApr 4, 2019
#72: support for "modularity" project extension
via ModularityExtension interface
1 parent 150179c commit d49e06b

File tree

6 files changed

+208
-0
lines changed

6 files changed

+208
-0
lines changed
 

‎README.md

+63
Original file line numberDiff line numberDiff line change
@@ -495,13 +495,72 @@ patchModules.config = [
495495
Compilation
496496
===
497497

498+
Compilation to a specific Java release
499+
----
500+
501+
You might want to run your builds on a recent JDK (e.g. JDK 12), but target an older version of Java, e.g.:
502+
- Java 11, which is the current [Long-Term Support (LTS) release](https://www.oracle.com/technetwork/java/java-se-support-roadmap.html),
503+
- Java 8, whose production use in 2018 was almost 85%, according to [this survey](https://www.baeldung.com/java-in-2018).
504+
505+
You can do that by setting the Java compiler [`--release`][javacRelease] option
506+
(e.g. to `6` for Java 6, etc.). Note that when you build using:
507+
- JDK 11: you can only target Java 6-11 using its
508+
[`--release`](https://docs.oracle.com/en/java/javase/11/tools/javac.html) option,
509+
- JDK 12: you can only target Java 7-12 using its
510+
[`--release`](https://docs.oracle.com/en/java/javase/12/tools/javac.html) option,
511+
- etc.
512+
513+
Finally, note that JPMS was introduced in Java 9, so you can't compile `module-info.java` to Java release 6-8
514+
(this plugin provides a workaround for that, though — see below).
515+
516+
Concluding, to configure your project to support JPMS and target:
517+
- Java **6-8**: call the [`modularity.mixedJavaRelease`][ModularityExtension] function
518+
(see [Separate compilation of `module-info.java`](#separate-compilation-of-module-infojava) for details),
519+
- Java **9+**: call the [`modularity.standardJavaRelease`][ModularityExtension] function,
520+
521+
and the plugin will take care of setting the [`--release`][javacRelease] option(s) appropriately.
522+
523+
498524
Separate compilation of `module-info.java`
499525
----
500526

501527
If you need to compile the main `module-info.java` separately from the rest of `src/main/java`
502528
files, you can enable `compileModuleInfoSeparately` option on `compileJava` task. It will exclude `module-info.java`
503529
from `compileJava` and introduce a dedicated `compileModuleInfoJava` task.
504530

531+
Typically, this feature would be used by libraries which target JDK 6-8 but want to make the most of JPMS by:
532+
- providing `module-info.class` for consumers who put the library on module path,
533+
- compiling `module-info.java` against the remaining classes of this module and against other modules
534+
(which provides better encapsulation and prevents introducing split packages).
535+
536+
This plugin provides an easy way to do just that by means of its
537+
[`modularity.mixedJavaRelease`][ModularityExtension] function, which implicitly sets
538+
`compileJava.compileModuleInfoSeparately = true` and configures the [`--release`][javacRelease] compiler options.
539+
540+
For example, if your library targets JDK 8, and you want your `module-info.class` to target JDK 9
541+
(default), put the following line in your `build.gradle(.kts)`:
542+
543+
<details open>
544+
<summary>Groovy DSL</summary>
545+
546+
```groovy
547+
modularity.mixedJavaRelease 8
548+
```
549+
550+
</details>
551+
<details>
552+
<summary>Kotlin DSL</summary>
553+
554+
```kotlin
555+
modularity.mixedJavaRelease(8)
556+
```
557+
558+
</details>
559+
560+
Note that `modularity.mixedJavaRelease` does *not* configure a
561+
[multi-release JAR](https://docs.oracle.com/javase/9/docs/specs/jar/jar.html#Multi-release)
562+
(in other words, `module-info.class` remains in the root directory of the JAR).
563+
505564
Limitations
506565
===
507566

@@ -524,3 +583,7 @@ Contributions are very much welcome.
524583
Please open a Pull Request with your changes.
525584
Make sure to rebase before creating the PR so that the PR only contains your changes, this makes the review process much easier.
526585
Again, bonus points for providing tests for your changes.
586+
587+
588+
[javacRelease]: http://openjdk.java.net/jeps/247
589+
[ModularityExtension]: src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java

‎src/main/java/org/javamodularity/moduleplugin/ModuleSystemPlugin.java

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import org.gradle.api.Project;
55
import org.gradle.api.plugins.ExtensionContainer;
66
import org.gradle.api.plugins.JavaPlugin;
7+
import org.javamodularity.moduleplugin.extensions.DefaultModularityExtension;
8+
import org.javamodularity.moduleplugin.extensions.ModularityExtension;
79
import org.javamodularity.moduleplugin.tasks.*;
810

911
public class ModuleSystemPlugin implements Plugin<Project> {
@@ -18,6 +20,7 @@ private void configureModularity(Project project, String moduleName) {
1820
ExtensionContainer extensions = project.getExtensions();
1921
extensions.add("moduleName", moduleName);
2022
extensions.create("patchModules", PatchModuleExtension.class);
23+
extensions.create(ModularityExtension.class, "modularity", DefaultModularityExtension.class, project);
2124

2225
new CompileTask(project).configureCompileJava();
2326
new CompileModuleInfoTask(project).configureCompileModuleInfoJava();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package org.javamodularity.moduleplugin.extensions;
2+
3+
import org.gradle.api.JavaVersion;
4+
import org.gradle.api.Project;
5+
import org.gradle.api.plugins.JavaPlugin;
6+
import org.gradle.api.tasks.compile.JavaCompile;
7+
import org.javamodularity.moduleplugin.JavaProjectHelper;
8+
9+
import java.util.List;
10+
11+
public class DefaultModularityExtension implements ModularityExtension {
12+
13+
private final Project project;
14+
15+
public DefaultModularityExtension(Project project) {
16+
this.project = project;
17+
}
18+
19+
@Override
20+
public void standardJavaRelease(int mainJavaRelease) {
21+
if (mainJavaRelease < 9) {
22+
throw new IllegalArgumentException(String.format(
23+
"Invalid main --release value: %d. Use 'mixedJavaRelease' instead.", mainJavaRelease
24+
));
25+
}
26+
project.afterEvaluate(p -> configureStandardJavaRelease(mainJavaRelease));
27+
}
28+
29+
private void configureStandardJavaRelease(int mainJavaRelease) {
30+
JavaCompile compileJava = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME);
31+
setJavaRelease(compileJava, mainJavaRelease);
32+
}
33+
34+
@Override
35+
public void mixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease) {
36+
validateMixedJavaReleaseArgs(mainJavaRelease, moduleInfoJavaRelease);
37+
38+
CompileModuleOptions moduleOptions = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME)
39+
.getExtensions().getByType(CompileModuleOptions.class);
40+
moduleOptions.setCompileModuleInfoSeparately(true);
41+
42+
project.afterEvaluate(p -> configureMixedJavaRelease(mainJavaRelease, moduleInfoJavaRelease));
43+
}
44+
45+
private static void validateMixedJavaReleaseArgs(int mainJavaRelease, int moduleInfoJavaRelease) {
46+
if (mainJavaRelease < 6) {
47+
throw new IllegalArgumentException("Invalid main --release value: " + mainJavaRelease);
48+
}
49+
if (mainJavaRelease > 8) {
50+
throw new IllegalArgumentException(String.format(
51+
"Invalid main --release value: %d. Use 'standardJavaRelease' instead.", mainJavaRelease
52+
));
53+
}
54+
if (moduleInfoJavaRelease < 9) {
55+
throw new IllegalArgumentException("Invalid module-info --release value: " + moduleInfoJavaRelease);
56+
}
57+
}
58+
59+
private void configureMixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease) {
60+
var compileJava = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME);
61+
setJavaRelease(compileJava, mainJavaRelease);
62+
63+
var compileModuleInfoJava = helper().compileJavaTask(CompileModuleOptions.COMPILE_MODULE_INFO_TASK_NAME);
64+
setJavaRelease(compileModuleInfoJava, moduleInfoJavaRelease);
65+
}
66+
67+
// TODO: Remove this method when Gradle supports it natively: https://github.com/gradle/gradle/issues/2510
68+
private void setJavaRelease(JavaCompile javaCompile, int javaRelease) {
69+
String currentJavaVersion = JavaVersion.current().toString();
70+
if (!javaCompile.getSourceCompatibility().equals(currentJavaVersion)) {
71+
throw new IllegalStateException("sourceCompatibility should not be set together with --release option");
72+
}
73+
if (!javaCompile.getTargetCompatibility().equals(currentJavaVersion)) {
74+
throw new IllegalStateException("targetCompatibility should not be set together with --release option");
75+
}
76+
77+
List<String> compilerArgs = javaCompile.getOptions().getCompilerArgs();
78+
if (compilerArgs.contains("--release")) {
79+
throw new IllegalStateException("--release option is already set in compiler args");
80+
}
81+
82+
compilerArgs.add("--release");
83+
compilerArgs.add(String.valueOf(javaRelease));
84+
}
85+
86+
private JavaProjectHelper helper() {
87+
return new JavaProjectHelper(project);
88+
}
89+
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.javamodularity.moduleplugin.extensions;
2+
3+
/**
4+
* A project-wide extension that provides the most common modularity-related actions.
5+
*
6+
* @see DefaultModularityExtension
7+
*/
8+
public interface ModularityExtension {
9+
10+
/**
11+
* Calling this method results in all Java classes being compiled to Java release 9+ (as given by the
12+
* {@code mainJavaRelease} parameter).
13+
* <p>
14+
* See details about the {@code --release} option
15+
* <a href="https://docs.oracle.com/en/java/javase/11/tools/javac.html">here</a>.
16+
*
17+
* @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task (allowed range: 9+)
18+
*/
19+
void standardJavaRelease(int mainJavaRelease);
20+
21+
/**
22+
* Calling this method results in all Java classes being compiled to Java release 6-8 (as given by the
23+
* {@code mainJavaRelease} parameter), with the exception of {@code module-info.java} being compiled to
24+
* Java release 9.
25+
*
26+
* @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task (allowed range: 6-8)
27+
*/
28+
default void mixedJavaRelease(int mainJavaRelease) {
29+
mixedJavaRelease(mainJavaRelease, 9);
30+
}
31+
32+
/**
33+
* Calling this method results in all Java classes being compiled to Java release 6-8 (as given by the
34+
* {@code mainJavaRelease} parameter), with the exception of {@code module-info.java} being compiled to
35+
* Java release 9+ (as given by the {@code moduleInfoJavaRelease} parameter).
36+
* <p>
37+
* See details about the {@code --release} option
38+
* <a href="https://docs.oracle.com/en/java/javase/11/tools/javac.html">here</a>.
39+
*
40+
* @param mainJavaRelease value for the {@code --release} option of {@code compileJava} task
41+
* (allowed range: 6-8)
42+
* @param moduleInfoJavaRelease value for the {@code --release} option of {@code compileModuleInfoJava} task
43+
* (allowed range: 9+)
44+
*/
45+
void mixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease);
46+
}

‎test-project-kotlin/greeter.api/build.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ tasks {
1818
}
1919
}
2020
}
21+
22+
modularity {
23+
}
2124
//endregion
2225

2326
val compileKotlin: KotlinCompile by tasks

‎test-project/greeter.api/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@ test {
1616
runOnClasspath = false
1717
}
1818
}
19+
20+
modularity {
21+
}
1922
//endregion

0 commit comments

Comments
 (0)
Please sign in to comment.