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]
The input class can be a path name to a .class file, a directory, a JAR file, or it can be a fully qualified class name to analyze all class files. The options determine the output. By default, the jdeps command writes the dependencies to the system output.
The input class can be a path name to a .class file, a directory, a JAR file, or it can be a fully qualified class name to analyze all class files. The options determine the output. By default, the jdeps command writes the dependencies to the system output. The command can generate the dependencies in DOT language (see the -dotoutput option).
jdeps [options] path ... Command-line options. For detailed descriptions of the options that can be used, see A pathname to the .class file, directory, or JAR file to analyze. The jdeps command shows the package-level or class-level dependencies of Java class files.
Warning: The JDK internal APIs are inaccessible. Specifies where to find class files. Specifies the module path. Specifies the upgrade module path. Specifies an alternate system module path. --add-modules module-name [, module-name...] Adds modules to the root set for analysis.
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:
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)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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With