Skip to content
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

Gradle build overrides Log4J and Slf4J classes and fails to compile code that uses these libraries #900

Open
rizsi opened this issue Mar 28, 2024 · 5 comments
Assignees
Labels

Comments

@rizsi
Copy link

rizsi commented Mar 28, 2024

If a project uses the log4j or the SLF4J API then when it is compiled then the version used by the build tool instead of the version provided by the gradle configuration is used.
The version used by the build tool is not compatible with current teaVM (9.2.0) and compile to JS fails.
This makes it impossible to use these logging APIs and compile the code with gradle.

A minimal project with 2 Java files and gradle configuration that reproduces the issue is attached. In this project the org.apache.log4j.Logger class gets a trivial re-implementation but the compiler uses the original version of this class even though the original version is not referenced in the gradle file at all. report.tar.gz

I suspect that the problem is caused by that the InProcessBuildStrategy.buildClassloader() classloader that is used to load classes of the code to compile preloads the SLF4J and log4j implementations because they are configured when loading the InProcessBuildStrategy class and this method uses its own classloader to bootstrap the classloader hierarchy.

I have tested executing the same build using the direct configuration of org.teavm.tooling.TeaVMTool and that works the code is compiled and the result works as expected.

The error message (part of the log is omitted to only show the essence):

Class java.util.concurrent.LinkedBlockingQueue was not found
    at org.slf4j.LoggerFactory.replayEvents(LoggerFactory.java:206)
    at org.slf4j.LoggerFactory.postBindCleanUp(LoggerFactory.java:183)
    at org.slf4j.LoggerFactory.bind(LoggerFactory.java:177)
    at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
    at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
    at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
    at org.apache.log4j.Category.<init>(Category.java:57)
    at org.apache.log4j.Logger.<init>(Logger.java:37)
    at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:43)
    at org.apache.log4j.Logger.getLogger(Logger.java:41)
    at org.apache.log4j.Logger.getLogger(Logger.java:49)
    at example.MainClass.main(MainClass.java:7)

Method java.lang.ClassLoader.getResources(Ljava/lang/String;)Ljava/util/Enumeration; was not found
Class java.util.concurrent.CopyOnWriteArrayList was not found
Class java.lang.reflect.Proxy was not found

(To make SLF4J work with TeaVM the references to the missing JVM features also have to be removed from SLF4J code but that is not too much work and is an acceptable workaround for our goals.)

@konsoletyper konsoletyper self-assigned this Mar 31, 2024
@konsoletyper
Copy link
Owner

As a workaround you can try to use out-of-process build

@rizsi
Copy link
Author

rizsi commented Apr 2, 2024

Thanks! TeaVM is a great project everything I tried works perfectly in the browser. I only miss reflection because then I could do RPC simpler but it can also be worked around. It makes my old dream possible to program the browser in Java running on the client side.
I have worked it around executing it directly calling TeaVMTool from own code. We have an exotic build system (OSGI bundle manifest based) and it was even simpler to integrate it this way.
I solved same VM execution with a trick that loads the TeaVM compiler directly over the boot classloader so nothing else is accessible in its classloader. And then it is executed reflectively so the codes using loggers need not be present in the same classloader as the compiler itself.

For me it is solved with the workaround but I think the bug can discourage other developers to use TeaVM when their simplest examples don't compile due to using Log4j or SLF4J APIs.

@konsoletyper
Copy link
Owner

As for RPC: don't use reflection for it. If you have a chance to find reflection-less (for example, based on annotation processors) library or to write it yourself, please, do! Reflection is usually pain-in-the-ass for AOT compilers, Graal Native Image or proguard-based builds suffer from this issue as well

@rizsi
Copy link
Author

rizsi commented Apr 2, 2024

Yes, you are right, thanks! I have done a simple code generator that serializes the public fields and public accessors and knows the basic types, arrays and few from the collection framework. It will be enough for my goals and the footprint will be minimal. Remoting also automatically transforms a Java interface to one that returns promise wrapped objects on the client side. I have tried the primitives that it uses and they work . But it is not ready yet.

@rizsi
Copy link
Author

rizsi commented Apr 2, 2024

For the record configuring compilation out of process solves issue. The configuration in build.gradle that works:

teavm.js {
    addedToWebApp = true
    mainClass = "example.MainClass"
    outOfProcess = true
    
    // this is also optional, default value is <project name>.js
    targetFileName = "example.js"
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants