-
-
Notifications
You must be signed in to change notification settings - Fork 639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multi-Release JAR that contains Java 8 binaries + Java 9 module-info.class #2230
Comments
Hi @danieldietrich, In this case I assume that what you want to produce is a real module (under Java 9+), and hope that your jar will not break clients using Java 8 (saying this because libraries that scan jars will fail on your module-info file if they think it's a class and that's one of the reasons mrjars aren't cool). If all you want to do is reserve a module name for the future, then the So, if you want to have 2 sets of things that end up in a jar, so really, really want to produce a mrjar, it's not really different from, for example, having generated sources, compiling them and packaging in the jar eventually. Here it will involve:
So, first step, create a
Next, configure the Java compile tasks:
Then, package everything into the jar:
And that's all! The following build scan shows that when running the |
BTW if you want to do this on all modules of your project, I'd suggest you make this a |
Hi @melix, thank you for the details, I can reproduce it. But I still wasn't able to glue your hints with my Java 9 modules build (see below). Basically, the module-info.java can't be compiled because the dependent modules and exported packages cannot be found.
Root Project 'vavr'settings.gradle: rootProject.name = "vavr"
include 'vavr-core'
include 'vavr-control' build.gradle if (!JavaVersion.current().java9Compatible) {
throw new GradleException("Please build Vavr with JDK 9+")
}
subprojects {
afterEvaluate {
repositories {
jcenter()
}
group = 'io.vavr'
version = '1.0.0'
sourceSets {
java9 {
java {
srcDirs = ['src/main/java9']
}
}
}
compileJava {
sourceCompatibility = 8
targetCompatibility = 8
options.encoding = 'UTF-8'
}
compileJava9Java {
sourceCompatibility = 9
targetCompatibility = 9
options.encoding = 'UTF-8'
inputs.property("moduleName", moduleName)
doFirst {
options.compilerArgs = [ '--module-path', classpath.asPath ]
classpath = files()
}
}
jar {
into('META-INF/versions/9') {
from sourceSets.java9.output
}
manifest.attributes(
'Multi-Release': 'true',
)
}
}
} Subproject 'vavr-core'build.gradle plugins {
id 'java-library'
}
ext.moduleName = 'io.vavr.core' src/main/java9/module-info.java module io.vavr.core {
exports io.vavr.core;
} (src/main/java omitted here) Subproject 'vavr-control'build.gradle plugins {
id 'java-library'
}
ext.moduleName = 'io.vavr.control' src/main/java9/module-info.java module io.vavr.control {
exports io.vavr.control;
} (src/main/java omitted here) |
@melix I think I'm nearly there. The main problem is, that the
|
Maybe the classpath system and the module system are now mixed-up somehow... 🤔 |
@Opalo I saw on StackOverflow that you are familiar with Gradle sourceSet magic. Could you please take a look at the v1.0.0 branch? git clone https://github.com/vavr-io/vavr.git
cd vavr
git checkout v1.0.0
./gradlew assemble The build needs to be run with JDK9. |
@Opalo @melix sorry for flooding you with messages - no further actions are needed. I moved a step back and implemented a simple Java 8 multi-project build. That's the best solution for now, it will work with Java 8 and Java 9 and we do not risk any side-effects by piggy-backing the module-info.class in the jar. subprojects {
apply plugin: 'java'
repositories {
jcenter()
}
group = 'io.vavr'
version = '1.0.0'
compileJava {
sourceCompatibility = 8
targetCompatibility = 8
options.encoding = 'UTF-8'
options.compilerArgs = [ '-Xlint:all', '-Werror' ]
}
afterEvaluate {
jar {
inputs.property('moduleName', moduleName)
manifest.attributes(
'Automatic-Module-Name': moduleName
)
}
}
} |
Out of curiosity, why did you put the |
@melix if I omit
build.gradle (failing): subprojects {
apply plugin: 'java'
repositories {
jcenter()
}
group = 'io.vavr'
version = '1.0.0'
compileJava {
sourceCompatibility = 8
targetCompatibility = 8
options.encoding = 'UTF-8'
options.compilerArgs = [ '-Xlint:all', '-Werror' ]
}
jar {
inputs.property('moduleName', moduleName)
manifest.attributes(
'Automatic-Module-Name': moduleName
)
}
} vavr-control/build.gradle: dependencies {
compile project(':vavr-core')
}
ext.moduleName = 'io.vavr.control' vavr-core/build.gradle: ext.moduleName = 'io.vavr.core' |
I see, it's an ordering problem. You have to define the
|
@melix I haven't written a Gradle plugin, yet. Are there any good resources on that topic? Do I need This does not work: // parent build.gradle
class Module {
String name
}
class modularity implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('module', Module)
}
}
subprojects {
apply plugin: 'java'
apply plugin: 'modularity'
repositories {
jcenter()
}
group = 'io.vavr'
version = '1.0.0'
compileJava {
sourceCompatibility = 8
targetCompatibility = 8
options.encoding = 'UTF-8'
options.compilerArgs = [ '-Xlint:all', '-Werror' ]
}
jar {
println "Creating module $module.name"
manifest.attributes(
'Automatic-Module-Name': module.name
)
}
} // subproject build.gradle
module {
name = 'io.vavr.core'
} Error: Plugin with id 'modularity' not found. |
@melix Update: it does work. I needed to remove the quotes Thx for the hint! |
Yes, using |
@melix Actually it did not work because the original Gradle module name was taken (I think When I renamed it to I ended up with a more straight-forward solution without extra plugins - I just renamed the Gradle modules of the multi module project. build.gradle: subprojects {
apply plugin: 'java'
repositories {
jcenter()
}
group = 'io.vavr'
version = '1.0.0'
compileJava {
sourceCompatibility = 8
targetCompatibility = 8
options.encoding = 'UTF-8'
options.compilerArgs = [ '-Xlint:all', '-Werror' ]
}
jar {
manifest.attributes(
'Automatic-Module-Name': module.name
)
}
} The subprojects only define dependencies, no more extra module names are declared. |
@melix oh, that's not possible because the maven coordinates changed, too 🙈 |
@melix Sorry for the flood of notifications. I run into more problems.
class JavaModule {
String name
}
class modularity implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('javaModule', JavaModule)
project.task('jarModule') {
doLast {
println "Automatic-Module-Name: $extension.name"
// FAILS
jar {
manifest.attributes(
'Automatic-Module-Name': extension.name
)
}
}
}
}
}
subprojects {
apply plugin: 'java'
apply plugin: modularity
repositories {
jcenter()
}
group = 'io.vavr'
version = '1.0.0'
compileJava {
sourceCompatibility = 8
targetCompatibility = 8
options.encoding = 'UTF-8'
options.compilerArgs = [ '-Xlint:all', '-Werror' ]
}
jar {
dependsOn 'jarModule'
}
} Subproject: javaModule { name = 'io.vavr.core' } Error: $ ./gradlew clean assemble
Execution failed for task ':vavr-core:jarModule'.
> Could not find method jar() for arguments [modularity$_apply_closure1$_closure2$_closure3@410542b4] on task ':vavr-core:jarModule' of type org.gradle.api.DefaultTask. |
@danieldietrich here you have a simple workaround:
What you want to do is quite tricky since you have configured a cycle. Just replace root |
@Opalo thanks, let's come back this evening. (The subproject name isn't the java module name. Currently we have 'vavr-core' and 'vavr-control' but we need 'io.vavr.core' and 'io.vavr.control'. However, the artifact names need to remain 'vavr-core' and ' vavr-control'.) |
So add this to
Of course map ( |
Thanks, have fun! |
@Opalo I reverted the Plugin-related changes and went back to the simplest solution. We gained nothing by adding additional lines of codes and workarounds - the result is the same with the following: subprojects {
apply plugin: 'java'
repositories {
jcenter()
}
group = 'io.vavr'
version = '1.0.0'
compileJava {
sourceCompatibility = 8
targetCompatibility = 8
options.encoding = 'UTF-8'
options.compilerArgs = [ '-Xlint:all', '-Werror' ]
}
afterEvaluate {
jar {
inputs.property('moduleName', moduleName)
manifest.attributes(
'Automatic-Module-Name': module.name
)
}
}
} (+ ext.moduleName definitions in the subproject's build.gradle files). Simplicity wins. |
Yes, definitely. This is a very good piece of gradle configuration :) |
WhatIf you're still interested in providing Java 9 Note that it won't produce a Multi-Release JAR, but it doesn't seem necessary to me (rationale here). WhyI really hope you're still interested in it, because if popular libraries like yours don't adopt JPMS, we all (as a community) won't be able to benefit from it. There's a recent post by Nicolas Fränkel about how hardly any popular library is modularized yet. On the other hand, one of these libraries (JUnit 5) is again working on support for JPMS (5 drafts, 2 working): junit-team/junit5#1091 👍 So I hope you'll at least consider it... HowYou just need to:
plugins {
// your current plugins here
id 'org.javamodularity.moduleplugin' version '1.5.0' apply false
}
subprojects {
apply plugin: 'org.javamodularity.moduleplugin'
modularity.mixedJavaRelease 8 // sets "--release 8" for main code, and "--release 9" for "module-info.java"
// test.moduleOptions.runOnClasspath = true // optional (if you want your tests to still run on classpath)
// your current subproject configuration here
} No need for custom source sets, etc. PS. Note that you can't set |
@tlinkowski as long as we support Java 8, we will stay with automatic-module-name only. Sorry, but I think it is not worth the effort for the moment. |
@danieldietrich I could be wrong about this, but from reading @tlinkowski's comment and from what I've observed of the JUnit 5 folks' work towards modularity, I was under the impression that there is a way of including |
I see. And if I found the time to provide a PR for this, would you be willing to accept it (provided its quality were OK)? Or are you generally opposed to the idea of providing a Java 9
Yes, precisely. |
I do not want to mix up JDK8 binaries/classes with a JDK9 module-info.class in one .jar file. Furthermore, we will not create multi-release .jars in order to separate JDK8 and JDK9 builds. Expect the unexpected - it will lead to problems. (I have been there with class names containing spec-conform unicode characters but tools/cloud platforms errored when processing them). I don‘t see any problem you try to solve here. But I see problems that might be introduced when applying this change. VAVR is both, classpath and module-path compatible. That should be enough for now. I will not accept a pull request, the risk is too high to break Java 8 environments. I care about Java 8 because, as you said and as our users reflected, most Java users are still on Java 8. The existing tooling is a smell. It looks like a workaround. The Java language architects should work on a proper solution. Developers should not have the option to choose. There should be exactly one simple way to create .jars for all. |
I see. Well, I trust your judgment — my experience is scarce here.
I understand — you see negative gain/cost ratio here. For the record, though, the problem I'm trying to solve is: inappropriately weak encapsulation prevailing in the Java ecosystem. It's not that you can't write software without stronger encapsulation (surely, people do, so you can — we had so many years without JPMS to get used to it). It's just that, with stronger encapsulation, you can write software better and write better software, I believe. Analogy (not very strong, though):
Stretching this analogy even further:
But like I said, I understand & respect your stance! I just disagree that JPMS wouldn't help solve any problems for VAVR (both for its maintainers and for its users) in the long term.
Understood.
I agree with every sentence! Still, I think that what JPMS brings is worth it, even despite all these hurdles. But it's just my opinion now (subject to change with increasing experience). |
I really appreciate your engagement for bringing Java forward. For VAVR itself and for consumers of VAVR, encapsulation/modularization is not a big deal. We decided to ship VAVR as one standalone jar, because it is small enough. Slicing it into modules that contain only two or three public classes would be too much.
You see, currently I have no specific use-case for the module-info DSL. |
@danieldietrich I would really want this to be able to use vavr in a context where I want users of my library to be able to use jlink. I know this is a niche use case and I'm probably the first to ask for that reason but...yeah. |
I need a helping hand regarding our Gradle build for Vavr 1.0.0 (see v1.0.0 branch).
We have
Additionally we need
The
build.gradle
needs to be altered in the way that./vavr-<module>/build/lib/vavr-<module>-<version>.jar
-release 8
compiler argmodule-info.java
is compiled with JDK9-release 9
compiler argResources
The text was updated successfully, but these errors were encountered: