Developing IntelliJ Plugins - workspace setup Skip to content →

Developing IntelliJ Plugins – workspace setup

I’ve spent the last few weeks configuring and developing IntelliJ plugins. This post is a quick summary of what I’ve learned so far. Some of the things I discuss in this post are not documented and base on my own investigation and debugging of various IntelliJ IDE mechanisms.

Important note! I was working with IntelliJ IDEA Community Edition version 15.0.6. It’s been a long time since its release, and a lot of things may have changed by now. Nevertheless, version 15.0.6 is not outdated, considering the life cycle of an enterprise plugin.

Setting up developer workspace

You have at least two ways of setting up the development workspace. The first one assumes that you configure everything directly in the IDE. Such a setup is tightly coupled with the project’s configuration. At the beginning, you have to set up an SDK that will be used when running the sandbox. The SDK should contain libraries provided by IntelliJ Community Edition. Note that the versions of the libraries must be compatible with the IDE versions supported by the plugin. In the second step, you should configure the plugin’s module. At this point, the Plugin Deployment tab is most important. Verify that a proper path to the plugin.xml file is specified.
Next, you should create a run configuration. IntelliJ provides a dedicated run configuration type named “Plugin”. When you manually configure your run configuration, make sure that the “Use classpath of module” option is configured as desired. Moreover, this configuration should use the SDK created in the first step. When you manually configure your run configuration, select a proper value for the “Use classpath of module” option, and ensure that the configuration uses the SDK you created in step one.

Gradle’s plugin

Another way to set up the workspace is to use the Gradle extension for the IntelliJ Platform developers named gradle-intellij-plugin. It allows you to configure the entire environment in a single build.gradle file. The biggest advantage of this solution is that it limits the time needed to make everything work. The build can be configured once, and then used on every developer machine. Additionally, it doesn’t have any dependencies to the IDE you use, which gives you a freedom of using your favorite IDE, not only IntelliJ.

Dependencies between plugins

Things get a bit more complicated when there are two dependent plugins. The very first thing to begin with is the concept of plugin class loaders.

Plugin Class Loaders

In IntelliJ, every single plugin is loaded by a separate class loader. This implies one important thing. If you have two plugins, A and B, and plugin B uses classes from plugin A, then both A and B have to be loaded by the same class loader. Otherwise, any use of A’s classes in plugin B will cause java.lang.ClassCastException.

If you have the IDE-based configuration, add the plugin A’s libs to the SDK, and mark them as provided in the module configuration. If you get the CCE, it means that plugin A is loaded from the runtime environment, not from the sandbox. In such case, verify the module dependencies in the Project Structure section.

If you use the gradle-intellij-plugin plugin, you have to provide dependencies of plugin A in the dependency section (build.gradle file). It is also required to add plugin A in the ‘plugins’ section.

For both IDE-based and gradle-based configurations, you must provide the section in B’s plugin.xml configuration.

Transitive dependencies

This paragraph assumes that you use the gradle-intellij-plugin plugin.

When you start the runIde task, it will deploy plugin B and run the sandbox (with plugin A pre-installed). Let’s consider the following architecture:

Gradle will deploy jar files for all of the dependencies of plugin B (B.plugin-submodule.jar, B.submodule-1/2/3.jar, A.submodule-1/2.jar). This means that there are two sources of jars for plugin A. The first one is the path where the plugin has been pre-installed (IntelliJ path/plugins/a/libs). The second one is the deployment target (sandbox/plugins/b/libs). Circumstances like this will cause CCE, because different class loaders will be used.
To solve this issue, you must remove all of the A.* jars from the Gradle configuration by disabling the transitive dependencies:

Unfortunately, such a configuration will cause another problem. With the following configuration:

for the B.plugin-submodule, the B.submobule-4 dependency should be provided by B.submodule-3. With the transitive dependencies disabled, the B.submobule-4 dependency won’t be loaded. You will have to add this dependency in the B.plugin-submodule straightway.

Honestly, I haven’t found a good solution to this problem. Adding all of the dependencies that were transitive by default is not a handy solution. It may cause a lot of runtime exceptions, especially NoClassDefFound exception. It seems that only the dependencies that match the pattern (A.*) should be removed from runtime.

disabled_plugins.txt

When you are developing the plugin, many exceptions may be thrown. The ones thrown at the bootstrap time are especially dangerous. Imagine the plugin has an error that causes java.lang.RuntimeException. During the first run, IntelliJ notifies you that the plugin has been disabled. The second run is a bit more interesting. Nothing happens. The plugin is just not started.

Let’s take a look at the PluginManagerCore class, especially the shouldLoadPlugin() method.

If the plugin’s id is not equal to “com.intellij”, or the id is not saved in the “idea.load.plugins.id” property, or its category is not listed in the “idea.load.plugins.category” property, then the list of disabled plugins is checked (getDisabledPlugins).

Where are these disabled plugins stored? Go to getDisabledPlugins() and then to loadDisabledPlugins() where you can find the disabled_plugins.txt file. The file is stored in the sandbox/config directory.
Every time an error occurs while bootstrapping the plugin, its id is added to the file. In order to enable the plugin, you have to remove the id from the file.

Setting up the workspace was not fast and easy. There were many problems I had to solve by debugging IntelliJ sources. This took a lot of time, but it gave me a better understanding of the underlying mechanisms.

Published in Programming