jdeps can't print-module-deps due to a MultiReleaseException
Asked Answered
I

1

10

We have a JavaFX based application which is not modularized (there are reasons, a legacy library is involved) but we build an custom runtime using jdeps and jlink.

We've recently rewritten the app and added a couple of new dependencies, as well as removing others. Now the script that is building the application suddenly stopped working during the jdeps call.

Note: This is happening on Linux – I've yet to test other OS'ses, but I don't expect another result.

When the script calls

~/path/to/jdk/bin/jdeps -q --multi-release 11 --ignore-missing-deps --print-module-deps --class-path ~/path/to/app/target/package/libs/* target/classes/ch/cnlab/uxtest/MainKt.class

the result is always

Exception in thread "main" java.lang.Error: java.util.concurrent.ExecutionException: com.sun.tools.jdeps.MultiReleaseException
    at jdk.jdeps/com.sun.tools.jdeps.DependencyFinder.waitForTasksCompleted(DependencyFinder.java:271)
    at jdk.jdeps/com.sun.tools.jdeps.DependencyFinder.parse(DependencyFinder.java:133)
    at jdk.jdeps/com.sun.tools.jdeps.DepsAnalyzer.transitiveArchiveDeps(DepsAnalyzer.java:217)
    at jdk.jdeps/com.sun.tools.jdeps.DepsAnalyzer.run(DepsAnalyzer.java:138)
    at jdk.jdeps/com.sun.tools.jdeps.ModuleExportsAnalyzer.run(ModuleExportsAnalyzer.java:74)
    at jdk.jdeps/com.sun.tools.jdeps.JdepsTask$ListModuleDeps.run(JdepsTask.java:1047)
    at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:574)
    at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:533)
    at jdk.jdeps/com.sun.tools.jdeps.Main.main(Main.java:49)
Caused by: java.util.concurrent.ExecutionException: com.sun.tools.jdeps.MultiReleaseException
    at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
    at jdk.jdeps/com.sun.tools.jdeps.DependencyFinder.waitForTasksCompleted(DependencyFinder.java:267)
    ... 8 more
Caused by: com.sun.tools.jdeps.MultiReleaseException
    at jdk.jdeps/com.sun.tools.jdeps.VersionHelper.add(VersionHelper.java:62)
    at jdk.jdeps/com.sun.tools.jdeps.ClassFileReader$JarFileReader.readClassFile(ClassFileReader.java:360)
    at jdk.jdeps/com.sun.tools.jdeps.ClassFileReader$JarFileIterator.hasNext(ClassFileReader.java:402)
    at jdk.jdeps/com.sun.tools.jdeps.DependencyFinder.lambda$parse$5(DependencyFinder.java:179)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)

I couldn't find much in regards to this specific exception and everything I've found so far was not applicable to our situation.

To not wait as long as the build takes to reach this point, I've let the program print out the command before it gets executed and use it on a terminal. Then it get's a bit weirder:

Exception in thread "main" java.lang.module.FindException: Module org.slf4j not found, required by com.dlsc.gmapsfx
        at java.base/java.lang.module.Resolver.findFail(Resolver.java:893)
        at java.base/java.lang.module.Resolver.resolve(Resolver.java:192)
        at java.base/java.lang.module.Resolver.resolve(Resolver.java:141)
        at java.base/java.lang.module.Configuration.resolve(Configuration.java:421)
        at java.base/java.lang.module.Configuration.resolve(Configuration.java:255)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsConfiguration$Builder.build(JdepsConfiguration.java:564)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.buildConfig(JdepsTask.java:603)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:557)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:533)
        at jdk.jdeps/com.sun.tools.jdeps.Main.main(Main.java:49)

Why of all sudden to I get a different exception? Which one is correct now? I have no clue.
Fact is, the jar containing org.slf4j only has the automatic module name.

I really have have no idea, what I should do with little information... If someone else can point to something, I'd be glad.

Thanks, Daniel

PS: The following code prints the command and executes it:

echo "detecting required modules"
CMD="$JDK/bin/jdeps -q --multi-release ${JAVA_VERSION} --ignore-missing-deps --print-module-deps --class-path ${OUT_LIBS}/* ${MAIN_CLASS_FILE}"; echo "$CMD"
detected_modules=$("$JDK"/bin/jdeps -q \
  --multi-release ${JAVA_VERSION} \
  --ignore-missing-deps \
  --print-module-deps \
  --class-path "${OUT_LIBS}/*" \
    "${MAIN_CLASS_FILE}") || exit
echo "detected modules: ${detected_modules}"

They really seem to create different result... 🀷

PPS: If I remove the --multi-release part, I get a different error, that jackson is multi-release, but I need to specify what I want...

Error: jackson-core-2.13.0.jar is a multi-release jar file but --multi-release option is not set

Edit #1

In the pom file, we have the following deps

<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-base</artifactId>
    <version>${use.javafx.version}</version>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-controls</artifactId>
    <version>${use.javafx.version}</version>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-fxml</artifactId>
    <version>${use.javafx.version}</version>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-graphics</artifactId>
    <version>${use.javafx.version}</version>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-web</artifactId>
    <version>${use.javafx.version}</version>
</dependency>

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
    <version>${kotlin.version}</version>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-reflect</artifactId>
    <version>${kotlin.version}</version>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-test-junit</artifactId>
    <version>${kotlin.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-core</artifactId>
    <version>${kotlinx.coroutines.version}</version>
</dependency>
<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-jdk8</artifactId>
    <version>${kotlinx.coroutines.version}</version>
</dependency>

<dependency>
    <groupId>io.insert-koin</groupId>
    <artifactId>koin-core-jvm</artifactId>
    <version>${koin.version}</version>
</dependency>
<dependency>
    <groupId>io.insert-koin</groupId>
    <artifactId>koin-test-jvm</artifactId>
    <version>${koin.version}</version>
</dependency>

<dependency>
    <groupId>org.simpleframework</groupId>
    <artifactId>simple-xml</artifactId>
    <version>${simplexml.version}</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>${log4j.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${log4j.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>${log4j.version}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>${jackson.version}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>${jackson.version}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson.version}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-kotlin</artifactId>
    <version>${jackson.version}</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>${okhttp.version}</version>
</dependency>
<dependency>
    <groupId>net.sf.proguard</groupId>
    <artifactId>proguard-base</artifactId>
    <version>${proguard.version}</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.kordamp.ikonli</groupId>
    <artifactId>ikonli-materialdesign2-pack</artifactId>
    <version>${ikonli.mdi2.version}</version>
</dependency>
<dependency>
    <groupId>org.kordamp.ikonli</groupId>
    <artifactId>ikonli-javafx</artifactId>
    <version>${ikonli.version}</version>
</dependency>
<dependency>
    <groupId>org.controlsfx</groupId>
    <artifactId>controlsfx</artifactId>
    <version>${controlsfx.version}</version>
</dependency>
<dependency>
    <groupId>com.dlsc</groupId>
    <artifactId>GMapsFX</artifactId>
    <version>${gmapfx.version}</version>
</dependency>

<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>${sqlite.jdbc.version}</version>
</dependency>

<!-- some custom deps from our company -->

<dependency>
    <groupId>org.conscrypt</groupId>
    <artifactId>conscrypt-openjdk</artifactId>
    <version>${conscrypt.version}</version>
    <classifier>${os.detected.classifier}</classifier>
</dependency>

Resulting in the following JAR files (minus our own):

annotations-13.0.jar
apiguardian-api-1.1.2.jar
conscrypt-openjdk-2.5.2-linux-x86_64.jar
controlsfx-11.1.0.jar
GMapsFX-11.0.2.jar
hamcrest-core-1.3.jar
ikonli-core-12.2.0.jar
ikonli-javafx-12.2.0.jar
ikonli-materialdesign2-pack-12.2.0.jar
jackson-annotations-2.13.0.jar
jackson-core-2.13.0.jar
jackson-databind-2.13.0.jar
jackson-module-kotlin-2.13.0.jar
jakarta.activation-1.2.2.jar
jakarta.activation-api-1.2.2.jar
jakarta.xml.bind-api-2.3.3.jar
jaxb-impl-2.3.3.jar
junit-4.12.jar
junit-jupiter-api-5.8.1.jar
junit-jupiter-engine-5.8.1.jar
junit-platform-commons-1.8.1.jar
junit-platform-engine-1.8.1.jar
koin-core-jvm-3.1.3.jar
koin-test-jvm-3.1.3.jar
kotlin-reflect-1.5.31.jar
kotlin-stdlib-1.5.31.jar
kotlin-stdlib-common-1.5.30.jar
kotlin-stdlib-jdk7-1.5.31.jar
kotlin-stdlib-jdk8-1.5.31.jar
kotlin-test-1.5.31.jar
kotlin-test-annotations-common-1.5.30.jar
kotlin-test-common-1.5.30.jar
kotlin-test-junit-1.5.31.jar
kotlinx-coroutines-core-1.5.2.jar
kotlinx-coroutines-core-jvm-1.5.2.jar
kotlinx-coroutines-jdk8-1.5.2.jar
log4j-api-2.14.1.jar
log4j-core-2.14.1.jar
log4j-web-2.14.1.jar
logback-classic-1.2.3.jar
logback-core-1.2.3.jar
okhttp-4.9.2.jar
okio-2.8.0.jar
opentest4j-1.2.0.jar
proguard-base-6.2.2.jar
simple-xml-2.7.1.jar
slf4j-api-1.7.29.jar
sqlite-jdbc-3.36.0.3.jar
stax-1.2.0.jar
stax-api-1.0.1.jar
xpp3-1.1.3.3.jar

The Java version in use is Bell Soft's Full Liberica JDK 17, we used all recent versions since 14 or 15 where it worked well prior to the rebuild of the UI.


edit #2:

The result of

mvn compile org.apache.maven.plugins:maven-dependency-plugin:3.1.1:resolve -DexcludeTransitive

is

[INFO] The following files have been resolved:
[INFO]    com.fasterxml.jackson.core:jackson-databind:jar:2.13.0:compile -- module com.fasterxml.jackson.databind
[INFO]    com.squareup.okhttp3:okhttp:jar:4.9.2:compile -- module okhttp3 [auto]
[INFO]    com.fasterxml.jackson.core:jackson-annotations:jar:2.13.0:compile -- module com.fasterxml.jackson.annotation
[INFO]    org.openjfx:javafx-fxml:jar:17.0.1:compile -- module javafx.fxmlEmpty [auto]
[INFO]    org.openjfx:javafx-web:jar:17.0.1:compile -- module javafx.webEmpty [auto]
[INFO]    org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:1.5.31:compile -- module kotlin.stdlib.jdk8
[INFO]    io.insert-koin:koin-core-jvm:jar:3.1.3:compile -- module koin.core.jvm (auto)
[INFO]    org.jetbrains.kotlin:kotlin-test-junit:jar:1.5.31:test -- module kotlin.test.junit
[INFO]    org.simpleframework:simple-xml:jar:2.7.1:compile -- module simple.xml [auto]
[INFO]    org.apache.logging.log4j:log4j-core:jar:2.14.1:compile -- module org.apache.logging.log4j.core [auto]
[INFO]    io.insert-koin:koin-test-jvm:jar:3.1.3:compile -- module koin.test.jvm (auto)
[INFO]    org.junit.jupiter:junit-jupiter-engine:jar:5.8.1:test -- module org.junit.jupiter.engine
[INFO]    org.xerial:sqlite-jdbc:jar:3.36.0.3:compile -- module org.xerial.sqlitejdbc [auto]
[INFO]    net.sf.proguard:proguard-base:jar:6.2.2:runtime -- module proguard.base (auto)
[INFO]    org.openjfx:javafx-graphics:jar:17.0.1:compile -- module javafx.graphicsEmpty [auto]
[INFO]    org.jetbrains.kotlinx:kotlinx-coroutines-core:jar:1.5.2:compile -- module kotlinx.coroutines.core (auto)
[INFO]    org.apache.logging.log4j:log4j-web:jar:2.14.1:compile -- module org.apache.logging.log4j.web [auto]
[INFO]    ch.<ourgroup>:<our-artifact>:1.8.8:compile -- module <our-lib> (auto)
[INFO]    org.openjfx:javafx-controls:jar:17.0.1:compile -- module javafx.controlsEmpty [auto]
[INFO]    org.controlsfx:controlsfx:jar:11.1.0:compile -- module org.controlsfx.controls
[INFO]    com.fasterxml.jackson.core:jackson-core:jar:2.13.0:compile -- module com.fasterxml.jackson.core
[INFO]    com.fasterxml.jackson.module:jackson-module-kotlin:jar:2.13.0:compile -- module com.fasterxml.jackson.kotlin
[INFO]    org.apache.logging.log4j:log4j-api:jar:2.14.1:compile -- module org.apache.logging.log4j
[INFO]    org.openjfx:javafx-base:jar:17.0.1:compile -- module javafx.baseEmpty [auto]
[INFO]    org.junit.jupiter:junit-jupiter-api:jar:5.8.1:test -- module org.junit.jupiter.api
[INFO]    org.kordamp.ikonli:ikonli-javafx:jar:12.2.0:compile -- module org.kordamp.ikonli.javafx
[INFO]    com.dlsc:GMapsFX:jar:11.0.2:compile -- module com.dlsc.gmapsfx
[INFO]    org.jetbrains.kotlin:kotlin-reflect:jar:1.5.31:compile -- module kotlin.reflect
[INFO]    org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:jar:1.5.2:compile -- module kotlinx.coroutines.jdk8 (auto)
[INFO]    org.kordamp.ikonli:ikonli-materialdesign2-pack:jar:12.2.0:compile -- module org.kordamp.ikonli.materialdesign2
[INFO]    org.conscrypt:conscrypt-openjdk:jar:linux-x86_64:2.5.2:compile -- module org.conscrypt [auto]
Illiberal answered 12/11, 2021 at 13:46 Comment(9)
Can you share a list of all JAR files (or/and their Maven coordinates) being on the class path? Or is the entire app and its build configuration available online? – Harem
Which jdeps version is running here? What does ~/path/to/jdk/bin/java -version print? – Harem
I can certainly share the jar file, but the app itself is not open source, gimme a second and I'll edit the question. – Illiberal
Thanks for listing them -- will check if they are listed as "suspicious" here github.com/sormuras/modules#suspicious-artifacts One more thing: what does "mvn compile org.apache.maven.plugins:maven-dependency-plugin:3.1.1:resolve -DexcludeTransitive" print? – Harem
Looking at the jdeps source for the first error: github.com/openjdk/jdk17u/blob/master/src/jdk.jdeps/share/… it should be printing a message like: github.com/openjdk/jdk17u/blob/master/src/jdk.jdeps/share/… i.e. it seems like it's complaining that the same class has multiple versions. But, that should be fine I think... – Selfsacrifice
@JornVernee thanks for looking into it as well. One question might be: Why doesn't jdeps print the message in the stack trace. That probably would help a bit. Is there a way to make the command more verbose (the option seem to do something else)? – Illiberal
Note: When I use the one-line command (instead of the multi-line one) and remove the dependency to gmapfx, I'm greeted with the same error, but for junit. Since junit makes no sense to keep in the final build, I filtered them out. Now I'm back with the not-so-helpful MultiReleaseException. Once again I'm back to 🀷... πŸ˜‰ – Illiberal
I think the error message not being printed correctly is a bug, and I've filed JDK-8277123 for it. Maybe you could find out which jar file it's failing on? (e.g. try running all jars through jdeps one by one), and then check what is contained in META-INF/versions in the failing jar. I think that would help to create a smaller reproducer. – Selfsacrifice
Okay, the exception you are seeing is a bug as well, likely caused by 2 different jar files having a module-info.class file in a different versioned directory (or just a class with the same name, but that seems less likely) JDK-8277165. The reason it's sometimes not happening is likely because there's a data race going on as well JDK-8277166. – Selfsacrifice
S
10

Update: These issues have been fixed, and a patched version of jdeps is available as part of the early access build for JDK 18 at: http://jdk.java.net/18/ (starting from build 26)


Turning my comments into an answer. There seem to be 3 bugs going on here:

  1. The MultiReleaseException seems to be because jdeps can not handle classes in different jars that have the same name, such as module-info.class, but are stored in a different META-INF/versions/xxx directory. (JDK-8277165)
  2. The fact that this exception is sometimes suddenly not occuring seems to be the result of a race condition in the code that checks for the above; classes of the same name having multiple versions. (JDK-8277166)
  3. The MultiReleaseException is missing it's exception message since it's thrown as part of an asynchronous task, which wraps it in an ExecutionException, which then leads to jdeps not reporting the exception correctly. (JDK-8277123)

As for a workaround, I don't think there's a good one at this point, except maybe for editing all the jars on the class path so that they put the module-info.class in the same META-INF/versions/xxx directory (but, this might have other consequences as well, so you probably don't want to run with the edited jars, and only use them for jdeps).

Selfsacrifice answered 17/11, 2021 at 20:23 Comment(1)
Thank you very much for the effort and for raising the issues. I still could try to test each JAR as you suggested, but for now I've returned to the simplest approach: I jlink a tailored runtime based on a fix list of modules. This is not ideal, since it may miss modules or contain too much, but is OK for the moment. But I will get back to your suggestion, as soon as we are required to go live with the application. – Illiberal

© 2022 - 2024 β€” McMap. All rights reserved.