Support of multiple IntelliJ IDEA versions

Is there an "IntelliJ IDEA" way of making a plugin that is compatible with multiple versions while using newer features not all versions support?

I'm modifying a plugin that is compatible with 8.x. I want to keep the compatibility.
But I want to use the DumbAware interface for an Action which was introduced with 9.x.
Also I use DataManager.getInstance().getDataContext() which is deprecated since 9.x and should be replaced by a call to DataManager.getInstance().getDataContextFromFocus().getResult() which is not available in 8.x.

For the getDataContext() call I could either live with the deprecated call and hope for the method staying in place, or I could make a distinction about which method to use according to ApplicationInfo.

But for the Action I don't really see a way except just not implementing DumbAware and living with it.

Are really the above the only ways for this or is there a better way?
If you now want to suggest to release multiple versions with differing since-build and until-build, this is of course possible, but much more maintenance work than releasing just a single version, so I'd prefer to have one version if possible.

20 comments

Hello Björn,

There's unfortunately no good solution for this problem. One option is to
have multiple versions of "facade" classes around the API and link the common
plugin codebase with a specific facade version to get a plugin for IDEA 8
or IDEA 9. For example, the facade for IDEA 8 would have a MyDumbAwareAction
class that simply extends AnAction, and the facade for IDEA 9 would have
it extend AnAction and implement DumbAware.

Is there an "IntelliJ IDEA" way of making a plugin that is compatible
with multiple versions while using newer features not all versions
support?

I'm modifying a plugin that is compatible with 8.x. I want to keep the
compatibility.

But I want to use the DumbAware interface for an Action which was
introduced with 9.x.

Also I use DataManager.getInstance().getDataContext() which is
deprecated since 9.x and should be replaced by a call to
DataManager.getInstance().getDataContextFromFocus().getResult() which
is not available in 8.x.

For the getDataContext() call I could either live with the deprecated
call and hope for the method staying in place, or I could make a
distinction about which method to use according to ApplicationInfo.

But for the Action I don't really see a way except just not
implementing DumbAware and living with it.

Are really the above the only ways for this or is there a better way?

If you now want to suggest to release multiple versions with differing
since-build and until-build, this is of course possible, but much more
maintenance work than releasing just a single version, so I'd prefer
to have one version if possible.


--
Dmitry Jemerov
Development Lead
JetBrains, Inc.
http://www.jetbrains.com/
"Develop with Pleasure!"


0

But I have to register the Action through the plugin.xml, don't I?
And I didn't see a way to give a factory method so that I can return either an action that implements DumbAware or an action that doesn't implement DumbAware according to build number.
Or did I miss something?

0

Hello Björn,

But I have to register the Action through the plugin.xml, don't I?


Yes.

And I didn't see a way to give a factory method so that I can return
either an action that implements DumbAware or an action that doesn't
implement DumbAware according to build number.


You have a single class in your plugin code that inherits from a facade class,
and the facade class has different implementations for different API versions.
Then you build two versions of the plugin distribution with different facade
versions and different since/until build ranges.

--
Dmitry Jemerov
Development Lead
JetBrains, Inc.
http://www.jetbrains.com/
"Develop with Pleasure!"


0

But having to have different builds with different since/until build ranges was what I wanted to prevent if possible. :-)


I found that I can register the action programmatically and thus choose which to register according to build number with:

ActionManager.getInstance().registerAction(..., ..., ...);
((DefaultActionGroup) ActionManager.getInstance().getParentGroup(..., ..., ...)).addAction(..., ...);


But from where would be the appropriate place to do it in the IntelliJ IDEA architecture?

0

Hello Björn,

Action registration would have to be done from an ApplicationComponent, either
in the constructor or in the initComponent() method.

But having to have different builds with different since/until build
ranges was what I wanted to prevent if possible. :)

I found that I can register the action programmatically and thus
choose which to register according to build number with:

ActionManager.getInstance().registerAction(..., ..., ...);

((DefaultActionGroup) ActionManager.getInstance().getParentGroup(...,
..., ...)).addAction(..., ...);

But from where would be the appropriate place to do it in the IntelliJ
IDEA architecture?


--
Dmitry Jemerov
Development Lead
JetBrains, Inc.
http://www.jetbrains.com/
"Develop with Pleasure!"


0

Yep, that works great.
After having found the right methods to register the action and add it to a menu that works with IDEA 8, 9 and 10 and after finding out that I have to use a factory for the DumbAware action it works fine now in all three versions with one build.
The factory was necessary, because if I instantiate the DumbAware action in the same class as the non DumbAware action, the ClassLoader tries to load both actions, even if the DumbAware action is not used.

Thanks
Dmitry

0

One more question Dmitry, if i change mind and choose to support muliple versions by multiple packages with slightly different files tailored to specific versions, is there a way to publish them "as one" plugin with different artifacts for different version ranges or would I have to publish the artifacts as completely independent plugins with different names?

0

Hello Björn,

You can publish them as a single plugin with different since/until build
ranges for different versions.

One more question Dmitry, if i change mind and choose to support
muliple versions by multiple packages with slightly different files
tailored to specific versions, is there a way to publish them "as one"
plugin with different artifacts for different version ranges or would
I have to publish the artifacts as completely independent plugins with
different names?


--
Dmitry Jemerov
Development Lead
JetBrains, Inc.
http://www.jetbrains.com/
"Develop with Pleasure!"


0

So if I want to continually support two different versions I would have to always release two different versions?

So e. g.

1.0.1 (until 8)
1.0.2 (since 9)

next release

1.1.1 (until 8)
1.1.2 (since 9)

and so on?

0

Hello Björn,

Exactly.

So if I want to continually support two different versions I would
have to always release two different versions?

So e. g.

1.0.1 (until 8)
1.0.2 (since 9)
next release

1.1.1 (until 8)
1.1.2 (since 9)
and so on?


--
Dmitry Jemerov
Development Lead
JetBrains, Inc.
http://www.jetbrains.com/
"Develop with Pleasure!"


0

Hm, not too nice, but better than having to have separate plugins, thanks.

Hm, btw. are there restrictions on the version?
could it also be: 1.0-up_to_idea_8?
Or does it have to be only dots and numbers?
And if not, how is order determined?

0

Hello Björn,

Hm, not too nice, but better than having to have separate plugins,
thanks.

Hm, btw. are there restrictions on the version?
could it also be: 1.0-up_to_idea_8?
Or does it have to be only dots and numbers?
And if not, how is order determined?


See StringUtil.compareVersionNumbers() method in the CE source.

--
Dmitry Jemerov
Development Lead
JetBrains, Inc.
http://www.jetbrains.com/
"Develop with Pleasure!"


0

ok, great, thanks.

just for others as reference here:

there is no format restriction for versions except that if a part is numeric it must fit into an Integer
and to compare two versions the following is done:
- if only one is null, it is the lower version
- both versions are split at each dot, underscore and dash
- the resulting parts are compared
- if two parts to be compared both only consists of digits, an Integer compareTo is done
- otherwise a case-sensitive String compareTo is done
- if two versions have a different amout of parts and the amount of parts that both have is identical, the shorter version is the lower one

This means e. g.

- 1.0.0-6_and_older
- 1.0.0-7_to_8
- 1.0.0-9_and_newer

can be used and will show up in this order, lowest at the top in this list.

0

Dmitry is there a suggested project layout for such a setup?
One Module with common code and then multiple modules for the different versions that depend on the common module?
If so, what would be the type of the common module, also of Plugin type?
Or a Java module and the IntelliJ JARs as provided library?
Or even something completely different?

0

no idea / advice / experience anyone?


0

On a related note, is it possible to programatically register extensions? I have a similar use case to this where I would like to support different implementations of my custom language, which can significantly affect the language syntax and semantics. I'm currently using delegates to do this but it's pretty ugly - if I could switch once at startup on the configured implementation and register the relevant extensions rather than having to delegate, that would make my life much easier.

0

Hello Colin,

Yes, of course. Surprisingly enough, the name of the method that can be used
for this is ExtensionPoint.registerExtension(). :)

On a related note, is it possible to programatically register
extensions? I have a similar use case to this where I would like to
support different implementations of my custom language, which can
significantly affect the language syntax and semantics. I'm currently
using delegates to do this but it's pretty ugly - if I could switch
once at startup on the configured implementation and register the
relevant extensions rather than having to delegate, that would make my
life much easier.


--
Dmitry Jemerov
Development Lead
JetBrains, Inc.
http://www.jetbrains.com/
"Develop with Pleasure!"


0

Hehe, I never would have guessed what that did!

Are there rules about the lifecycle of doing this? Could I register a parser and a lexer in a ProjectComponent based on project configuration, for example? I've been meaning to investigate when these components are instantiated but I haven't done so yet. Specifically, to what extent can things related to language types be controlled at a project level - I'm guessing they're normally instantiated once and cached at application startup?

0

Hello Colin,

Actually for language-specific extensions like a ParserDefinition a better
API to use is LanguageExtension.addExplicitExtension().

You can register extensions at any time, the cache will be correctly updated
if you do that, but they have application scope - registering a different
ParserDefinition will affect all open projects, which is probably not what
you want.

Hehe, I never would have guessed what that did!

Are there rules about the lifecycle of doing this? Could I register a
parser and a lexer in a ProjectComponent based on project
configuration, for example? I've been meaning to investigate when
these components are instantiated but I haven't done so yet.
Specifically, to what extent can things related to language types be
controlled at a project level - I'm guessing they're normally
instantiated once and cached at application startup?


--
Dmitry Jemerov
Development Lead
JetBrains, Inc.
http://www.jetbrains.com/
"Develop with Pleasure!"


0

Hmm, no, that's definitely not what I want. I'll have to continue using the delegates then, I guess.

Thanks for the help!

0

Please sign in to leave a comment.