if-else logic in plugin dependencies?

Is there any way to implement conditions when declaring plugin dependencies in plugin.xml?

Let's say I want to implement some extensions if module com.intellij.modules.cidr.lang presents and some OTHER extensions if not. Something like next:

if <depends optional="true" config-file="clion.xml">com.intellij.modules.cidr.lang</depends>
else <depends optional="true" config-file="idea.xml"></depends>

My case: Since Android Studio 3.2 Canary 7, it has built-in CMake support (the same implementation as for CLion). Before I just checked for the presence of com.intellij.modules.cidr.lang or com.intellij.modules.java and use different extension's implementations for my cmake plugin in CLion and in Idea/AS:

<depends optional="true" config-file="clion.xml">com.intellij.modules.cidr.lang</depends>
<depends optional="true" config-file="idea.xml">com.intellij.modules.java</depends>

clion.xml

<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<annotator language="CMake" implementationClass="com.cmakeplugin.annotator.CMakeCLionAnnotator"/>
</extensions>
</idea-plugin>

idea.xml

<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<fileTypeFactory implementation="com.cmakeplugin.CMakeFileTypeFactory"/>
<lang.parserDefinition language="CMake" implementationClass="com.cmakeplugin.CMakeParserDefinition"/>
<lang.syntaxHighlighterFactory key="CMake" implementationClass="com.cmakeplugin.CMakeSyntaxHighlighterFactory"/>
<lang.braceMatcher language="CMake" implementationClass="com.cmakeplugin.CMakeBraceMatcher"/>
<lang.refactoringSupport language="CMake" implementationClass="com.cmakeplugin.CMakeRefactoringSupportProvider"/>
<annotator language="CMake" implementationClass="com.cmakeplugin.annotator.CMakeIdeaAnnotator"/>
<lang.findUsagesProvider language="CMake" implementationClass="com.cmakeplugin.CMakeFindUsagesProvider"/>
</extensions>
</idea-plugin>

I can't do that anymore for Android Studio 3.2 Canary 7 and above, as it has both that modules and I need to use only clion.xml for it.

Also, I would like to keep idea.xml functionality for AS 3.1 and below, so I can't just make a check for com.intellij.modules.androidstudio and use clion.xml for any AS. 

The best solution would be to use clion.xml if com.intellij.modules.cidr.lang presence and idea.xml otherwise. Does that possible somehow in terms of plugin.xml syntax?

0
5 comments

No, conditions aren't supported in plugin.xml file. However you can fix your case by replacing dependency on 'com.intellij.modules.java' by 'com.intellij.modules.idea'. The latter module is present in IntelliJ IDEA Community and Ultimate (since 2018.1) and isn't present in Android Studio.

1

Instead of providing a implementationClass use a factoryClass.  Implement the interface com.intellij.openapi.extensions.ExtensionFactory, and return the Annotator instance you want.  This will reduce your configuration file count to one for this piece of functionality.  The annotator element would look like this:

<annotator language="CMake" implementationClass="com.cmakeplugin.annotator.CMakeAnnotator" factoryClass="com.cmakeplugin.annotator.CMakeAnnotatorFactory"  />

In your CMakeAnnotatorFactory, you would use the PluginManager to detect the existence of the plugin you want to drive your functionality, then return an instance of the appropriate Annotator class.  Your implementationClass should be some common base class or interface.

Keep in mind I've never tried this so your mileage may vary, but it seems plausible.  If the user disables the plugin driving your functionality, they will have to restart IntelliJ anyway so you wouldn't need to care about handling changes in the plugin.

 

EDIT:   I just tested this out, and it works.

1

Thank you for your response. It's a shame we don't have that condition logic in plugin.xml. Conditioning dependencies will make very simple and elegant solution.

Unfortunately, replacing dependency on 'com.intellij.modules.java' by 'com.intellij.modules.idea' will disable functionality of `idea.xml` for all AS versions. While I would like to keep it for AS below 3.2 (current stable is 3.1.1).

Looks like I have to wait till AS 3.2 will be released and only then split my plugin functionality by 'com.intellij.modules.idea' (IDEA CE/UE) and `com.intellij.modules.cidr.lang` (CLion, AS)... But that will not work well for current AS Canary users and then for legacy AS users... 

PS Would be very useful to be able to check at plugin.xml for the presence of some particular class (not module) in platform under which plugin is running. Something like what we can do at runtime by checking `Class.forName(className)`. Looks like it's not to difficult to implement that in IdeaPluginDescriptorImpl.

0

Thank you @Bhandy! I've started looking to that direction of managing extensions from the java code somehow and even found quite promising method `com.intellij.lang.LanguageAnnotators.INSTANCE.addExplicitExtension()`. But you gave me really clear roadmap!

0

Recent update.

Dealing with `factoryClass` works fine if we need to provide a specific implementation (let say Annotator) depending on a platform we started under.

<annotator language="CMake"
factoryArgument="Annotator"
factoryClass="com.cmakeplugin.CMakeExtensionFactory" />
public class CMakeExtensionFactory implements ExtensionFactory {
@Override
public Object createInstance(String factoryArgument, String implementationClass) {
switch (factoryArgument) {
case "Annotator" : return CMakePDC.isCLION
? new CMakeCLionAnnotator()
: new CMakeIdeaAnnotator();
// ...
default: throw new java.lang.RuntimeException("Unknown factoryArgument for CMakeExtensionFactory: " + factoryArgument);
}
}
public class CMakePDC {
public static final boolean isCLION = isClass("com.jetbrains.cidr.cpp.cmake.CMakeLanguage");//PlatformUtils.isCLion();

private static boolean isClass(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
...

But in case if we should not provide any implementation for some of the platforms, we are in trouble. As far as `ExtensionFactory.createInstance` must return some kind of `Object` and that will be registered for extension point we call `createInstance` from.

In my case, that solution work fine for Annotators (I have either version for CLion and Idea), but fall with the error (CLion) or strange behaviour (AS 3.2 Canary 10) for `parserDefinition`, if I return either `null` or empty Object: `new Object()`.

The workaround would be creating  <application-components> to register extension explicitly:

<application-components>
<component>
<implementation-class>com.cmakeplugin.CMakeComponent</implementation-class>
</component>
</application-components>
public class CMakeComponent implements ApplicationComponent {
@Override
public void initComponent() {
if (CMakePDC.isCLION) return;
LanguageParserDefinitions.INSTANCE
.addExplicitExtension( CMakeLanguage.INSTANCE, new CMakeParserDefinition() );
...
}

Unfortunately, that (again) doesn't work for `findUsagesProvider` and (sometimes works, sometimes not) for `refactoringSupport`. It looks like because both of that extensions register default implementations: 

public class LanguageFindUsages extends LanguageExtension<FindUsagesProvider> {
public static final LanguageFindUsages INSTANCE = new LanguageFindUsages();

private LanguageFindUsages() {
super("com.intellij.lang.findUsagesProvider", new EmptyFindUsagesProvider());
}
}

and my ExplicitExtension for some reasons doesn't override it (bug? race conditions? ... I've been lost debugging through Idea source code...).

So, I have to use mixed solution with both ExtensionFactory and addExplicitExtension approaches:

<extensions defaultExtensionNs="com.intellij">
<fileTypeFactory implementation="com.cmakeplugin.CMakeFileTypeFactory"/>
<colorSettingsPage implementation="com.cmakeplugin.CMakeColorSettingsPage"/>
<annotator language="CMake"
factoryArgument="Annotator"
factoryClass="com.cmakeplugin.CMakeExtensionFactory" />
<lang.refactoringSupport language="CMake"
factoryArgument="refactoringSupport"
factoryClass="com.cmakeplugin.CMakeExtensionFactory" />
<lang.findUsagesProvider language="CMake"
factoryArgument="findUsagesProvider"
factoryClass="com.cmakeplugin.CMakeExtensionFactory" />
</extensions>

<application-components>
<component>
<implementation-class>com.cmakeplugin.CMakeComponent</implementation-class>
</component>
</application-components>
public class CMakeExtensionFactory implements ExtensionFactory {
@Override
public Object createInstance(String factoryArgument, String implementationClass) {
switch (factoryArgument) {
case "Annotator" : return CMakePDC.isCLION
? new CMakeCLionAnnotator()
: new CMakeIdeaAnnotator();
case "refactoringSupport" : return CMakePDC.isCLION
? new RefactoringSupportProvider() {}
: new CMakeRefactoringSupportProvider();
case "findUsagesProvider" : return CMakePDC.isCLION
? new EmptyFindUsagesProvider()
: new CMakeFindUsagesProvider();
default: throw new java.lang.RuntimeException("Unknown factoryArgument for CMakeExtensionFactory: " + factoryArgument);
}
}
}
public class CMakeComponent implements ApplicationComponent {
@Override
public void initComponent() {
if (CMakePDC.isCLION) return;
LanguageParserDefinitions.INSTANCE
.addExplicitExtension( CMakeLanguage.INSTANCE, new CMakeParserDefinition() );
SyntaxHighlighterFactory.LANGUAGE_FACTORY
.addExplicitExtension( CMakeLanguage.INSTANCE, new CMakeSyntaxHighlighterFactory() );
LanguageBraceMatching.INSTANCE
.addExplicitExtension( CMakeLanguage.INSTANCE, new CMakeBraceMatcher() );
}
}

Can't say I'm happy with the beauty of the final code. It works, but... I believe it should be a more elegant solution. Any case, I would be glad to hear any ideas and pieces of advice. And hope that will help other fellows who will stack in the future with the same problem.

PS Some promising methods might be among `com.intellij.openapi.extensions` and `com.intellij.core.CoreApplicationEnvironment` modules. In paticular:

com.intellij.openapi.extensions.Extensions.getRootArea().registerExtension()
com.intellij.core.CoreApplicationEnvironment.addExplicitExtension()

 

0

Please sign in to leave a comment.