beryx / badass-jlink-plugin

Create a custom runtime image of your modular application
https://badass-jlink-plugin.beryx.org
Apache License 2.0
387 stars 27 forks source link

"does not read module org.slf4j" after jlink in this case #224

Closed wkgcass closed 1 year ago

wkgcass commented 1 year ago

I came across this problem when I was calling vertx's TrustOptions.wrap(...) method. I made a simple demo project to reproduce this error, I will attach it into this issue.

vertx-slf4j-cannot-pass-jlink-demo.tar.gz

The demo is very simple, here's some critical code:

build.gradle

dependencies {
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.6'
    implementation group: 'org.slf4j', name: 'slf4j-simple', version: '2.0.6'
    implementation group: 'io.vertx', name: 'vertx-core', version: '4.3.7'
    implementation group: 'io.projectreactor.tools', name: 'blockhound', version: '1.0.6.RELEASE'
}

jlink {
    options = ['--compress', '2', '--no-header-files', '--no-man-pages']
    mergedModule {
        additive = true
        uses 'io.vertx.core.spi.VertxServiceProvider'
        uses 'io.vertx.core.spi.VerticleFactory'
        uses 'io.vertx.core.spi.JsonFactory'
        uses 'io.vertx.core.spi.launcher.CommandFactory'
    }
}

module-info.java

requires io.vertx.core;
requires org.slf4j;
requires org.slf4j.simple;

Main.java

private static final Logger logger = LoggerFactory.getLogger(Main.class);

public static void main(String[] args) {
    logger.info("into main()");
    TrustOptions.wrap(/*...some code...*/);
    logger.info("pass");
}

my results

when I run ./gradlew clean run, I get the following output:

[main] INFO demo.Main - into main()
[main] INFO demo.Main - pass

when I run ./gradlew clean jlink && ./build/image/bin/demo, I get the following output:

[main] INFO demo.Main - into main()
Exception in thread "main" java.lang.IllegalAccessError: class io.vertx.core.net.TrustManagerFactoryWrapper (in module org.example.merged.module) cannot access class org.slf4j.LoggerFactory (in module org.slf4j) because module org.example.merged.module does not read module org.slf4j
    at org.example.merged.module@1.0-SNAPSHOT/io.vertx.core.net.TrustManagerFactoryWrapper.<clinit>(TrustManagerFactoryWrapper.java:34)
    at org.example.merged.module@1.0-SNAPSHOT/io.vertx.core.net.TrustManagerFactoryOptions.<init>(TrustManagerFactoryOptions.java:65)
    at org.example.merged.module@1.0-SNAPSHOT/io.vertx.core.net.TrustOptions.wrap(TrustOptions.java:63)
    at demo/demo.Main.main(Main.java:15)

My demo main class can use slf4j, but the library (in merged module) cannot use it.

If I manually add requires org.slf4j in mergedModule { ... }, it cannot even complete the jlink task, an error will be thrown by javac: cannot find module org.slf4j.

I'm not familiar with jlink plugin, but I tried to do some debugging.

In the generated ModuleManager.java here, it only reads /module-info.class in a jar file, but for slf4j, its /module-info.class is in META-INF/versions/9/module-info.class I don't know whether this is related, but I post it here anyway.

Downgrading to a non-modular slf4j version can solve the problem, but I believe the modular version should always be preferred in the future.

airsquared commented 1 year ago

Because vertx doesn't have an explicit dependency on slf4j, you have to add addExtraDependencies 'slf4j' to the jlink block. Then you can add requires org.slf4j to the mergedModule block (the next version won't need this last step).

airsquared commented 1 year ago

Fixed in v3.0.0. Now just add addExtraDependencies 'slf4j' to the jlink block.