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 hiding some JRE packages from the plugin #696

Open
testforstephen opened this issue Nov 14, 2024 · 10 comments
Open

Support hiding some JRE packages from the plugin #696

testforstephen opened this issue Nov 14, 2024 · 10 comments

Comments

@testforstephen
Copy link

Use Case:
I'm working on a project that leverages javac as both a parser and compiler to provide Java language support. Previously, our tool relied on the javac bundled with the installed JDK, requiring users to install the latest JDK to get the new feature support. To eliminate this dependency, we are trying to embed a standalone javac JAR in our plugin to provide these capabilities independently of the system JDK.

However, a challenge arises because the embedded javac JAR contains classes that overlap with those in JRE modules, such as java.compiler and jdk.compiler. In practice, the JRE's classes take precedence, so the plugin classloader prioritizes the JRE versions of these classes over those in our embedded javac.

I'm looking for guidance on whether the OSGi platform provides a way to hide specific packages on the classpath from the plugin and allow it to use its own versions of overlapping classes.

@laeubi
Copy link
Member

laeubi commented Nov 14, 2024

@testforstephen due to technical limitations you cannot "replace" any package from the java.* namespace, but if you embedd the jar and simply don't import the package otherwise you will get it from your embedded jar (I assume its on the Bundle-Classpath...).

@merks
Copy link
Contributor

merks commented Nov 14, 2024

JustJ provides JREs that include javac:

https://eclipse.dev/justj/?page=documentation

image

I wonder if you can't just reuse that.

As @laeubi suggests, I don't think your use case can be supported...

@testforstephen
Copy link
Author

@merks @laeubi thanks for the response. Our project (JDT language server) uses javac API directly rather than invoking the javac.exe. The namespaces provided by the javac API are in the javax.* and com.sun.* packages. Currently, I’ve embedded a javac.jar in my bundle and set it on the Bundle-ClassPath in the bundle's MANIFEST.MF. Eclipse compiles this setup without issues, but at runtime, the bundle continues to use the javac API from the JRE instead of the embedded version.

If there is no existing solution for it yet, would it be possible to use a custom classloader to prioritize loading our local version over the JRE’s? Or could we extend the MANIFEST.MF to support an attribute like Hide-Classpath-Package to allow hiding packages on the classpath from the bundle?

@laeubi
Copy link
Member

laeubi commented Nov 14, 2024

javax.* and com.sun.* should be fine, you should check the following things:

  1. There is no Import-Package for these packages you want to use from the jar, otherwise they might get priority
  2. The jar is included in the binary build (e.g via build.properties)
  3. It could be that you need to disable delegate classlaoding with osgi.compatibility.bootdelegation=false see here but this depends on your deployment configuration.
  4. If it is allowed to embed /ship javac.jar from a legal perspective

@merks
Copy link
Contributor

merks commented Nov 14, 2024

I think you should check out what the JDT project is doing to enable using javac for building the abstract syntax tree. Maybe they already solved this problem?

@testforstephen
Copy link
Author

javax.* and com.sun.* should be fine, you should check the following things:

  1. There is no Import-Package for these packages you want to use from the jar, otherwise they might get priority
  2. The jar is included in the binary build (e.g via build.properties)
  3. It could be that you need to disable delegate classlaoding with osgi.compatibility.bootdelegation=false see here but this depends on your deployment configuration.
  4. If it is allowed to embed /ship javac.jar from a legal perspective

@laeubi I have configured the local javac.jar path in both Bundle-ClassPath of MANIFEST.MF and bin.includes of build.properties. And in the Import-Package attribute, I didn't configure javax.* and com.sun.* packages. In Eclipse, when I navigate to javac API references in my code, it correctly links to the local javac.jar.

However, during debugging the bundle, the Java runtime prioritizes the system module when finding javac api classes. I use the Java command line parameters -verbose:class --show-module-resolution to view the class/module loading logs, I can see that the runtime loads the javac classes from the JRE’s modules (e.g. java.compiler, jdk.compiler) instead of the local javac.jar. This indicates the bundle classloader prioritizes JDK classes over local classes.

I will take a look at osgi.compatibility.bootdelegation=false to see if it helps.

I think you should check out what the JDT project is doing to enable using javac for building the abstract syntax tree. Maybe they already solved this problem?

@merks JDT is using ECJ (Eclipse Compiler for Java) to build AST, the thing we're doing is to make JDT to support an alternative parser/compiler (e.g. javac).

@merks
Copy link
Contributor

merks commented Nov 14, 2024

I suggest you revisit what jdt is doing now because it’s doing exactly what you just stated.

@akurtakov
Copy link
Member

I suggest you revisit what jdt is doing now because it’s doing exactly what you just stated.

@merks What you speak about is being done in subproject of jdt - jdt.ls (https://projects.eclipse.org/projects/eclipse.jdt.ls) and is not (yet!) part of main jdt. Also @testforstephen is one of the main drivers of that works and his request is in order to make this work easier.
I hope this clears the confusion.

@laeubi
Copy link
Member

laeubi commented Nov 14, 2024

@testforstephen Its a bit hard to guess what happens, maybe you can share a small example?

In general the class-loading works like described here:

https://docs.osgi.org/specification/osgi.core/8.0.0/framework.module.html#i3174728

and as mentioned above, the only package that can't be overridden is java.* so there must be something fishy in your setup or interpretation of results.

@tjwatson
Copy link
Contributor

I agree with @laeubi there must be something strange with your bundle. Like requiring the system bundle or org.eclipse.osgi. Keep in mind that using require-bundle here will cause issues. For example, using Require-Bundle for org.eclipse.core.runtime will pull in all of org.eclipse.osgi which in-turn exposes you to all the JDK packages because this horrible re-export:

https://github.com/eclipse-platform/eclipse.platform/blob/6dd67323474b1b49d541bcd1e4ea8007ab2a36a4/runtime/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF#L12C17-L12C87

Something like that will prevent you from loading the packages from your bundle's class path.

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

5 participants