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

Support for Java 11 sources #256

Open
niloc132 opened this issue Feb 29, 2024 · 4 comments
Open

Support for Java 11 sources #256

niloc132 opened this issue Feb 29, 2024 · 4 comments
Assignees

Comments

@niloc132
Copy link
Member

While J2CL supports Java 11 sources, this plugin still does not - the build requirements are that we first run processors on source of a module, then strip @GwtIncompatible from original and generated sources, then produce "stripped" bytecode (from those stripped sources), and finally provide that bytecode as inputs to downstream j2cl invocations.

The obvious answer would be to change -source/-target or --release to invocations of javac to indicate 11 (or 17, etc). Unfortunately, that prevents any modules/dependencies from adding additional JRE emulation, since higher source/target/release values than 8 will cause JPMS limitations to kick in, and error out rather than create types in java.* packages.

The next few obvious, not-great answers, to get them out of the way:

  • stay on Java 8
  • always require upstream j2cl to have all the emulation anyone wants...
  • ...or allow someone to fork the upstream jre and build their own (this actually works already, see the config options for jreJar, jreJsZip, bootstrapClasspath, but note that they have to be pretty closely aligned with the j2cl version in use)

Examining the j2cl build wiring shows that one simple option is to pass both --patch-module java.base=. (to allow amending the base module) and --add-reads java.base=ALL-UNNAMED (so that any newly added classes are permitted to reference external classes). This however seems to make downstream code unable to interact with each other in some situations (which aren't yet fully clear to me) - see main...niloc132:j2clmavenplugin:support-java-11 for an example of this which passes the newly created integration test, but fails to build a simple archetype where src/main/java and src/test/java shared packages.

This seems to be a straightforward approach, and probably still worth pursuing, in case I'm just missing something dumb to let this work.

Another downside to this approach as written is that it ends up with this warning for each module:

[WARNING] java11-project-and-emul:emul:1.0/bytecode: system modules path not set in conjunction with -source 11

Note that I also tried passing --system <path to j2cl's generated image>, but this seems to prevent letting downstream modules of new jre emul use them, since they aren't part of that system image. But it does prevent the warning at least.


For producing "stripped" bytecode we've already replaced javac with turbine, since we don't need that bytecode to be executable. Doing the same for original bytecode could be an option, since turbine can run annotation processors - however, it would at least limit users from editing annotation processors in their project and expecting them to recompile on the fly. This seems like it could be a reasonable limitation though - GWT's Generators can't be recompiled on the fly like this (...without hot swapping the classes in the debugger), and annotation processors in GWT definitely require running an external build step. Still, worth considering, even if it strictly is a loss in functionality.

Taking this approach could also let us avoid running the first compile at all, in the cases where there are no annotation processors, or skip the source stripper if we already have bytecode and none of the types use @GwtIncompatible.


Next option would be to allow applications to list certain dependencies as providing emulation, and having an extra set of tasks which build only those first with the --patch-module, and build up a new set of bytecode, and jmod+jlink a new image to then pass as the system image to all other projects. It could get pretty messy here if we have to worry about dependencies between those emulation dependencies, unless we just make a single giant compile. If set up properly using existing caching tools, projects that share jre emul extension would still share compiled code.


Last option we've thought of so far: Look into using JDT as a compiler (and apt runner) instead of javac (or turbine). Early suggestions point to the JDT also being picky about JPMS, but I'd imagine that can be overcome. JDT is known for not being quite the same in a lot of ways as javac, but on the plus side, GWT and j2cl already use it, so bugs we hit are probably not going to severely impact projects in ways that they don't already.

@treblereel
Copy link
Collaborator

Thank you, Colin, for the detailed description of our options. Today, I added JdtBytecodeTask to j2cl-m-p, which uses JDT for compilation, included in the plugin.

It works, иге visually a little slower than the default Javac. I haven't checked APT yet, but I don't anticipate any problems. My test scenario used a maven reactor project with 3 modules:

module my.application {
    requires org.treblereel.module2;
    requires transitive org.treblereel.module1;
    requires elemental2.dom;
    exports org.treblereel;
}

module org.treblereel.module1 {
    exports org.treblereel.module1 to org.treblereel.module2, my.application;
}

module org.treblereel.module2 {
    requires org.treblereel.module1;
    exports org.treblereel.module2 to my.application;
}

As seen, my.application uses elemental2.dom, which was problematic for Javac. The only dirty trick had to be done with com.vertispan.j2cl-jre, because of java. and javax. classes, but for the bytecode task, we only need bazel-bin/jre/java/jre.js/javaemul from this dependency. So I filtered them out.

So, we need to solve the issue with projects like gwt-nio/gwt-time: we shouldn't include compiled classes of emulated APIs in jars. Plus, somehow exclude source emulated classes from compilation (BytecodeTask). I suggest using either .gwt.xml or a pre-agreed directory name for storing emulated classes, or a file descriptor in META-INF. Fortunately, there are few such projects.

@niloc132
Copy link
Member Author

Hmm - do we need the module info file for your jdt impl to work? I had hoped we could skip it entirely, not need it for a project or any dependencies. The purpose it serves in Java doesn't really apply when compiling to JS (and in fact barely applies for most Java project I've worked with in the last 10 years...), it would be disappointing to require this.

That said, if we could just add <bytecode>jdt</bytecode> (in <taskMappings>) as a way to enable to version you've built project-wide, it seems good to experiment with. I can likewise add a modified version of what i've written so far, as "java11PatchModule", and a turbine (with no option to build processors) task as some other name?

@niloc132
Copy link
Member Author

When this is closed, consider reverting #258

@treblereel
Copy link
Collaborator

treblereel commented Apr 13, 2024

@niloc132

I've explored all possible ways to utilize JDT, but I failed. The reason is the fact that annotation processing in JDT (3.37.0) is extremely unreliable and riddled with bugs.

For example, such as: eclipse-jdt/eclipse.jdt.core#1491 and many more, just try search

So, we'll have to come up with something using javac or abandon support for Java > 8 in the j2cl-maven-plugin.

my latest branch in case you wonna check it https://github.com/treblereel/j2clmavenplugin/tree/jdt_java11_task

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

No branches or pull requests

2 participants