External builder and bootclasspath

Hello all,

I need to add a jar to the "-Xbootclasspath/p:" of my external builder (it overwrites JavacTool to make it non-final so I can extend it). How is this possible?

If I add the jar to `<compileServer.plugin classpath="..."/>` it's already too late...

Thanks
Bastien

10 comments

Hi Bastien,

If you mean the compilation bootclasspath of the project being compiled, then placing any dependency before the "JDK" entry in module's settings will include it in the module's compilation bootclasspath.
If you mean bootclasspath of the build process itself, then it is not possible.

Could you please provide more details on the problem?

Eugene.

0

I have a custom "CeylonBuilder extends ModuleLevelBuilder", which tries to instantiate a class CeyloncTool. This CeyloncTool extends a modified version of JavacTool (changed to non-final). My problem is that instantiation is not possible because the class loader says I can't extend JavacTool (final class).

CeyloncTool and the modified JavacTool are both in a jar named "compiler.jar", so I think I need to put this jar in the bootclasspath of my external builder (so that my custom JavacTool is loaded before the original one).

It seems I'm fitting your second option, so it's not possible to do this?

0

I would suggest to package these compiler implementations in a separate jar and do not add this jar to jps-plugin's classpath at all (neither bootclasspath nor classpath). Instead, your builder implementation may create a classloader that dynamically loads compiler implementation from this jar. This is definitely more complicated solution than adding a compiler to a bootclasspath, but it is much more reliable: you can be sure that you use the exact version of the class that you need.
Another possibility: since you modify JavacTool class anyway, you can place the modified class into a different package, or simply change its name, and thus avoid class loading conflicts.

Regards,
  Eugene.

0

I'm also trying something else. Since I already created a custom SDK type (based on a JDK), I'm trying to force it as the JDK used to launch the build process. The downside of this is that I have to give it a Java version (let's say 1.7) instead of a Ceylon version (0.6.1). Otherwise my SDK won't be picked up.

Once my SDK is selected by BuildManager, I can override getToolsPath() to force my custom jar containing JavacTool, instead of the JDK's tools.jar.

I tried to create a custom classloader, but then it seems I have to do everything using reflection, otherwise my code uses the builder's classloader.
As for modifying JavacTool, i'd like to avoid this, because it is already used by the command-line compiler and the eclipse plugin. It would be nice if I could reuse as much as possible without modifying it (it was developed by other members of the Ceylon team anyway).

0

> I'm also trying something else. Since I already created a custom SDK type (based on a JDK), I'm trying to force it as the JDK used to launch the build process. The downside of this is that I have to give it a Java version (let's say 1.7) instead of a Ceylon version (0.6.1). Otherwise my SDK won't be picked up.

Unfortunately this is not a reliable solution too. If some project depends on several JDKs, the most recent will be chosen for build process running. This logic is fixed and cannot be overridden. Own classloader looks like the only "reliable" choice. You may split your compiler implementation into an implementation and well-defined interface (API). Then your builder will communicate with compiler via this API instead of reflection, while compiler implementation will be loaded by your custom classloader.

0

Thanks Eugene, I finally got it working without too much trouble. I created a "child first" classloader, which searches first in itself, then in the parent classloader and so on. It also handles a "blacklist" of packages/classes that must be reloaded even if they were already loaded (so that I can force-reload JavacTool).

But now I have another question. I would like my plugin to embed a small repository of jars, which have a structure similar to a maven repo:

repo/
  lib1/
    1.0/
       lib1-1.0.jar
  lib2/
    0.5/
       lib2-0.5.jar

etc.

Is it possible to copy the structure of this repo folder as-is to the build directory (and of course also in the plugin's final distribution zip), and use some of these jars as libraries in my plugin? If I create a library from these jars, they will be copied without their structure in the build directory... Also, if I set "repo" as a resources directory, it will be contained in myPlugin.jar. What I want is something like this:

myPlugin/
  lib/
    myPlugin.jar
    repo/
      lib1/...


And be able to use classes in lib1-1.0.jar from myPlugin.jar

Thanks again for your help.

0

Hi Bastien,
If I understood it right, this "repo" directory should not be located in the output directory, where compiled classes are stored and from which your main plugin jar will be created. Instead, we are talking about final "distribution package" of your plugin.
The "artifacts" feature (Project Structure | Artifacts) is flexible enough to create any directory layout you want. You can store the "repo" directory near your sources and set up your artifact to copy it "as is" to any place you want.
Alternatively you may use an ant script. In both cases you can make the ant script or the artifact to be run/built on every make. You can also set up several artifacts depending on each other, instead of one. In this case you may set only some of "sub-artifacts" to be built on make.
Hope this helps.

Eugene

0

Thanks, I'll try this. But if I make an Ant build file, how can I access the sandbox directory when I'm testing my plugin? (<sandbox path>/plugins/myPlugin/lib)
Also, is it possible to use some of these jars in the repo without having them copied and "flattened" in the lib directory?

0

Hello, Bastien,

IDEA loads only classes from jar files located directly in 'lib' directory of your plugin so if you want to load classes from other jars you need to
use your own classloader. In order to obtain path to the base directory of the plugin you can use something like
new File(PathUtil#getJarPathForClass(getClass())).getParentFile().

--
Nikolay Chashnikov
Software Developer
JetBrains
http://www.jetbrains.com
"Develop with pleasure!"

0

Hi Eugene,

I'm working together with Bastien and I'm now trying the artifact approach that you suggested. (In what I'm trying to achieve, there is no need for the jars I'm packing in a custom folder to be accessible to the plugin classloader since we're using a custom classloader anyway; they just need to be available on the correct (relative) path in the plugin distribution.)

I am able to create a correct artifact (jar); but I can't find a way to use this jar for running the plugin. In the Plugin Run Configuration, I can only specify a Module to use the classpath of, but I can't specify the artifact, so Idea will always "deploy" (ie. copy to plugins sandbox) what it normally packages but not the custom stuff I set up in the artifact.

One option would be to use an ant task to copy this stuff (here'd I'd still need an answer to Bastien's question about how to access the plugin sandbox path from an ant task), but I'd prefer to stick to Intellij's internal packaging capabilities (artifacts etc.)

Thanks!
Matija

0

Please sign in to leave a comment.