how to target multiple IDEs with language support

Answered

Hello,

I'm developing a plugin that should run on all intellij based IDEs with the specific language support for each.

the plugin interacts mainly with the editor and the PSI and needs to support all languages. currently supporting only java and python and i'm trying to understand how to setup the project.

I would like to do something like that:

plugin.xml:
<depends
optional="true"
config-file="my.plugin.id-with-python.xml">com.intellij.modules.python</depends>
<depends
optional="true"
config-file="my.plugin.id-with-java.xml">com.intellij.modules.java</depends>
my.plugin.id-with-python.xml:
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<projectService serviceImplementation="org.my.intellij.plugin.psi.python.PythonLanguageService"/>
</extensions>
</idea-plugin>
my.plugin.id-with-java.xml
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<projectService serviceImplementation="org.my.intellij.plugin.psi.java.JavaLanguageService"/>
</extensions>
</idea-plugin>

 

I tried setting a local development instance with idea IC or IU with python plugin installed ,Pythoid or PythonCore, i tried both:

intellij {
type.set("IC")
plugins.set(listOf("PythonCore","com.intellij.java"))
localPath.set("/opt/idea/idea-IC-213.7172.25")
}

this yields :

Cannot find builtin plugin 'PythonCore' for IDE: /opt/idea/idea-IC-213.7172.25

I managed to do it with a version for PythonCore:

intellij {
type.set("IC")
version.set("2021.3.3")
plugins.set(listOf("PythonCore:213.7172.26","com.intellij.java"))
}

that works. my PythonLanguageService and JavaLanguageService can compile with the specific language PSI classes.

i can execute runIde , install the python community plugin on the idea IC development instance and test java and python projects.

i can't use PC or PY as intellij.type because the java plugin is not there. but i can build the plugin and install it on pycharm and it works.

 

my question is what is the correct approach and project setup to achieve that? 

ideally i would like to just change intellij.type to one of IC,IU,PC,PY and just run a development instance.

another question i have is how to isolate the language specific dependencies from the rest of the project code? in a simple java multi module project i would probably put  PythonLanguageService in its own gradle module and make sure its compile classpath includes only the relevant dependencies. but how to do that in a plugin project where the gradle-intellij-plugin adds the dependencies from the extracted platform? is there maven coordinates for the various platform modules like for example the python PSI implementation?

 

0
4 comments

Hello Shalom Ben-Zvi,

First of all, I just started developing IntelliJ plugins, so maybe my answer might not be correct to 100%. I would like to share my experiences with you anyway.

If you like to target different Platforms (PyCharm, PhpStorm, IntelliJ Ultimate and so on) you should target IntelliJ Ultimate for compiling your plugin, so you can install all the languages you target.

I think this might fit your requirements when targeting Java and Python as supported languages:

intellij {
type.set("IU")
version.set("2021.3.3")
plugins.add("com.intellij.java")
plugins.add("com.intellij.modules.platform")
plugins.add("com.intellij.modules.python")
}

See Plugin Compatibility with IntelliJ Platform Products | IntelliJ Platform Plugin SDK (jetbrains.com) for a list of modules you can depend on.

Working with the optional dependencies in plugin.xml looks right to me. Just make sure you do not reference any service that has optional module dependencies from any general listener / extension point / service.

To test that you don't depend on IntelliJ Ultimate you can run your plugin directly in the targeted platform.
For example:

tasks {
runIde {
ideDir.set(File("C:\\Users\\nl\\AppData\\Local\\JetBrains\\Toolbox\\apps\\PhpStorm\\ch-0\\213.7172.28"))
}
}

Just point that path to your local installation of the IDE you like to test and debug your plugin in.

If you like to create one plugin that is compatible with multiple platforms you will have only a single build with all that code and only one gradle workflow I guess. For sure there are architectural possibilities to isolate your code but I think in the end you will always have one plugin artifact that is uploaded to the Plugin Store which contains the code for all IDEA versions and the IDEA decides which code is loaded and executed as defined in the plugin.xml

As I already said, I'm a newbie in Java and IDEA plugin development, but I hope these information help you.

Regards
Nico

0

Nico Löber Thank you for your response.

I tried your suggestion but plugins.add("com.intellij.modules.python") doesn't work

Error while evaluating property 'filteredArgumentsMap' of task ':compileKotlin'
   > Cannot find builtin plugin 'com.intellij.modules.python' for IDE: ~/.gradle/caches/modules-2/files-2.1/com.jetbrains.intellij.idea/ideaIU/2021.3.3/3b3afef02e84ba9da83f0d0f6f00e4a66b40e395/ideaIU-2021.3.3

 

My understanding is that intellij.plugins dsl expects plugins not modules.

I think that Intellij IU doesn't have the python plugin or modules unless you install python plugin.

 

0

Hi, Shalom Ben-Zvi

Your approach with IC type and Python/Java modules added is correct. That way, you can have both enabled at the same time. Using the IC with additional plugins/modules set is also required if you want to build one plugin to support multiple IDEs simultaneously. Switching between local paths is the most convenient way. 

Regarding languages isolation – optional configuration is also the right approach. Having two my.plugin.id-with-python.xml and my.plugin.id-with-java.xml will take care of the classpath in the IDE.

My understanding is that intellij.plugins dsl expects plugins not modules.
I think that Intellij IU doesn't have the python plugin or modules unless you install python plugin.

It accepts both – modules and plugins. Plugins can be external, hosted on the Marketplace or internal repository – in this case you have to specify the plugin version, like pluginId:version. Plugins can be passed as an artifact in the filesystem, or project() instance too.

In case of internal plugins – bundled within the IDE, like CSS, Java, etc – you don't have to provide their version, because the Gradle IntelliJ Plugin looks for them within the SDK classpath.

About modules – they are most likely internal plugins, but always delivered as a part of the SDK.

0

Hi, Jakub Chrzanowski . thank you for your response.

I think i got a setup that is good for my needs.

I created sub modules for each language/IDE i want to support , so i have java,python,rider,clion modules,
they all have gradle-intellij-plugin.

plugins {
    `java-library`
    id("org.jetbrains.intellij")
}

tasks{
    buildSearchableOptions {
        enabled = false
    }
    runIde{
        enabled = false
    }
}

and each adds a minimal intellij configuration, for example:


java:
intellij {
    version.set("IC-2021.3.3")
    plugins.set(listOf("com.intellij.java"))
}

python:
intellij {
    version.set("PC-2021.3.3")
    plugins.set(listOf("PythonCore"))
}
rider:
intellij {
    version.set("RD-2021.3.3")
    plugins.set(listOf("rider-plugins-appender"))
}


with this minimal configuration these modules produce only a simple jar that is consumed by the main plugin module. this also solves the question of isolating compile classpath in gradle build, each module compiles only with the relevant IDE.

the root module is the main plugin module, it consumes those language specific modules,has a dependency on IC,but does not require any plugins:

intellij {
    `java`
    pluginName.set(properties("pluginName"))
    version.set("2021.3.3")
    type.set("IC")
    /// no plugins plugins.set()
}

and my plugin.xml stays the same with optional dependencies on the various modules.

with that setup i can change the main module's intellij.type to any of jetbrains IDEs and run it with runIde. now they all run with the plugin,because the plugin module does not require any plugins from any IDE in gradle intellij extension. 

i hope this will prove to be a good setup , i still didn't experiment enough but I will need extension points from the various plugins so i hope it will be possible with that setup.

one drawback is the amount dist space for downloaded IDEs, all the IDEs are downloaded when building the project, that's no problem on developer machines where the gradle cache is kept, but its too much for github actions and i need to adjust the gradle cache there.

i also thought that i don't really need the whole IDE for compilation of the various modules, for example if i need to compile with the language PSI implementation i can probably do with just a gradle dependency:

compileOnly("com.jetbrains.intellij.java:java-psi:213.7172.25")

but i couldn't find the latest python psi only this old version:

compileOnly("com.jetbrains.intellij.python:python-psi:211.7628.21")

with this specific dependencies clean builds can b much faster and will takes less space on github CI.

but i couldn't find python-psi in any repository, that's one of my questions, where are all the jetbrains artifacts? i understand that gradle-intellij-plugin extract the IDE zip and builds the gradle compile classpath. but what if i wanted to choose those artifacts one by one from a maven repository?
for example is it possible to reference the java plugin by version instead of requiring the whole IDE,
like i did with the python plugin:


plugins.set(listOf("PythonCore:213.7172.26"))

but couldn't make it with the java plugin with any combination of name/version i tried.

i could download something with:


compileOnly("com.jetbrains.intellij.java:java:213.7172.25")


it looks like only the the java plugin classes, not the whole plugin ,is that good enough to compile for the java plugin support?

 

 

 

 

 

 

 

 

 

1

Please sign in to leave a comment.