Development IntelliJ Plugins

Revamping Plugins #3 – Migrating from DevKit to the Gradle build system

The Revamping Plugins series is about sharing the experience of updating outdated plugins to align with the latest IntelliJ Platform SDK guidelines. We hope this series will help you better understand how this is done and the tools JetBrains provides to simplify the process.

The IntelliJ Platform allows creating plugins and themes that extend the base functionalities of IntelliJ-based IDEs in many ways, such as adding language support, enhancing the build system and debugger, or simply changing the user interface.

Initially, it was possible to create such plugins using the JetBrains Project System (JPS). The bundled DevKit plugin helped set up the project scaffold and dependencies and provided some basic actions for building and testing it.

In 2015 we created the Gradle IntelliJ Plugin to improve this process and provide more possibilities for developers. The DevKit approach still exists, but we’re focused on the Gradle IntelliJ Plugin, recommending it as a powerful way of creating plugins for the IntelliJ-based IDEs.

I asked my colleagues at JetBrains when looking for an old plugin for IntelliJ-based IDEs based on DevKit. It turned out that Eugene Petrenko (@jonnyzzz) created a plugin eight years ago that is still in use! Thanks to Eugene, we’ll dedicate this episode to migrating the Jonnyzzz Dependencies plugin, which you can also find on GitHub: https://github.com/jonnyzzz/IDEA.Dependencies.

The Jonnyzzz Dependencies plugin provides an action to analyze projects and modules for unused dependencies in the source code. The latest 1.0.10 version was released on June 2, 2014. It is written in Java and contains a couple of actions for analyzing all or selected modules of the current project and one for removing unused dependencies. The Plugin’s compatibility was set to 133+, referring to the third release cycle of IntelliJ IDEA 2013.

The current report of the IntelliJ Plugin Verifier tool available on the JetBrains Marketplace plugin’s settings page highlights just a few issues that we will address after the migration to the Gradle build system.

Initial Gradle configuration

The first step of migration is to change the Project SDK selected for our project from the local IDEA SDK to plain Java 11 in the Project Structure preferences (⌘+;):

Project Structure view

That change will detach the project from the local IntelliJ SDK sources and mark all of the IDE-related code as unresolved. These sources will now be provided using the Gradle IntelliJ Plugin, a Gradle plugin that allows building plugins for the IntelliJ Platform using a specified IntelliJ target platform and bundled/third-party plugins without needing to provide their artifacts manually.

At the root of the project, create the build.gradle.kts Gradle file with the base configuration:

fun properties(key: String) = project.findProperty(key).toString()

plugins {
   // Java support
   id("java")
   // Gradle IntelliJ Plugin
   id("org.jetbrains.intellij") version "1.3.0"
}

group = properties("pluginGroup")
version = properties("pluginVersion")

// Configure project's dependencies
repositories {
   mavenCentral()
}

// Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin
intellij {
   pluginName.set(properties("pluginName"))
   version.set(properties("platformVersion"))
   type.set(properties("platformType"))

   // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
   plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty))
}

tasks {
   // Set the JVM compatibility versions
   properties("javaVersion").let {
       withType<JavaCompile> {
           sourceCompatibility = it
           targetCompatibility = it
       }
   }

   wrapper {
       gradleVersion = properties("gradleVersion")
   }

   patchPluginXml {
       version.set(properties("pluginVersion"))
       sinceBuild.set(properties("pluginSinceBuild"))
       untilBuild.set(properties("pluginUntilBuild"))
   }
}

The properties(key: String) helper function defined within this Gradle Kotlin DSL script loads all the configuration variables, like its name, version, and dependency versions from the gradle.properties file. In the long term, extracting variables that change over time into the external properties file helps manage the project’s configuration and keep the actual Gradle configuration clean.

# IntelliJ Platform Artifacts Repositories
# -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html

pluginGroup = com.eugenePetrenko.idea.dependencies
pluginName = Jonnyzzz Dependencies
# SemVer format -> https://semver.org
pluginVersion = 1.0.10

# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.
pluginSinceBuild = 203
pluginUntilBuild = 213.*

# IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
platformType = IC
platformVersion = 2020.3.4

# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins =

# Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3
javaVersion = 11

# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 7.3

# Opt-out flag for bundling Kotlin standard library.
# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
# suppress inspection "UnusedProperty"
kotlin.stdlib.default.dependency = false

The most significant changes introduced in the above configuration are:

  • Specifying the sinceBuild/untilBuild to 203-213.* to support the latest available release with the three previous major releases.
  • Using Java 11 instead of Java 8, because since the 2020.3 release IntelliJ-based IDEs use Java 11.
  • Using the latest available Gradle IntelliJ Plugin.

To apply the Gradle script to the project, invoke the Link Gradle Project action from the context menu of the build.gradle.kts file. This operation will replace the DevKit module and configure and download all required dependencies.

The current project structure doesn’t reflect the default Gradle structure, which assumes the default location of sources, tests, and resources as the src/ directory. To properly locate all the files, create /src/main/java, /src/main/resources, and /src/test/java directories and move any relevant content to them.

Adjusting Grade project structure
Adjusting Grade project structure

After linking the Gradle project, a significant number of imports from project dependencies are correctly resolved. To ensure that nothing is missing, build the project using the buildPlugin task. The following issues are still unresolved after building the plugin:

  • cannot find symbol class PsiClass
  • cannot find symbol variable InheritanceUtil
  • cannot find symbol variable LibraryPresentationManager
  • incompatible types: Vector<LibrariesSelectionDialog.DependencyNodeBase> cannot be converted to Vector<TreeNode>

The first one is related to the PSI, which is available in the Java module – the one that is missing for now. Adding the dependency to the com.intellij.java solved this issue and the next two as well. Now our gradle.properties file contains:

platformPlugins = com.intellij.java

Remember to add the Java module dependency to the plugin.xml file:

<idea-plugin>
  ...
  <depends>com.intellij.java</depends>
  ...
</idea-plugin>

The latest one was handled by simply changing the children vector type to Vector<TreeNode>.

Deprecated code handling

In the above Gradle configuration and its properties files, you may notice structured and documented sections. They come directly from the IntelliJ Platform Plugin Template, a repository that provides a curated setup to simplify the development of plugins. As the current configuration will stay minimal for educational purposes, don’t hesitate to review the curated solution for more benefits of tools provided by JetBrains.

As a next step, it is always worth reaching for the low-hanging fruit – Code Inspection. With the Code > Inspect Code… action, the IDE provides a full report of the project’s condition. For the current project, there are two errors, ninety-eight warnings, one weak warning, and fifty-four typos spotted. Most of them can be solved using the Alt+Enter shortcut that provides Quick Fixes for the given context.

When updating the IntelliJ SDK platform version to 2020.3.4, some methods could become deprecated, so it is crucial to follow the instructions provided with the @Deprecated annotation or JavaDocs.

During the refactoring, two types of deprecations were noticed:

  • Invoking the execute() : RunResult<T> method of WriteAction<T>
  • Usage of com.intellij.util.containers.Predicate<T> interface

The first case suggests using run(ThrowableRunnable) instead, so the change looks as follows:

// before
new WriteAction() {
  @Override
  protected void run(@NotNull Result result) {
    // ...
  }
}.execute();

// after
WriteAction.run(() -> {
  // ..
})

In the second issue, it was required to change the com.intellij.util.containers.Predicate import package and predicate .call(argument) invitation to java.util.function.Predicate and .test(argument).

No other suggestions than migrating from Java 8 to Java 11 had to be handled.

Verification with different IDEs

The rule of thumb is that the target platform used by the plugin – the one set as the intellij.version – is also the minimum version of the supported IDEs. If the plugin’s target platform is defined as 2020.3.4 (the latest available patch of the 2020.3 release cycle), the sinceBuild property must be specified as 203. The untilBuild property shouldn’t be lower, and due to the assumption that there are no breaking changes introduced in such a group, it can match all patches from that branch, like 203.*.

However, if there are new releases of the IntelliJ-based IDEs available that the plugin is about to support, it’s worth reviewing the API Changes section of the IntelliJ SDK Documentation. Because the IntelliJ Platform is constantly evolving, the Incompatible API Changes and Notable API Changes listings contain every significant change which may break the plugin implementation. You may also be interested in the IntelliJ Platform Verifier tool which is available as a standalone CLI and part of JetBrains Marketplace plugin update flow, and the runPluginVerifier Gradle task integrated within the Gradle IntelliJ Plugin.

With the preconfigured runPluginVerifier task, it is possible to check the plugin binaries against the IDEs that match a given since/until build scope and platform type.

Running Plugin Verifier

For the current project, the following IntelliJ IDEA versions were listed by the listProductsReleases task: IC-2020.3.4, IC-2021.1.3, IC-2021.2.3, IC-2021.3.

The first one is the version we use for developing the plugin, and there are no issues since they were already resolved. All of the others are always worth investigating.

The plugin verification output marked the first two versions as compatible, but since IC-2021.2.3, the following deprecation is reported:

Deprecated constructor com.intellij.notification.Notification.<init>(String, String, String, NotificationType, NotificationListener) invocation

Go to the Notification class of the relevant IntelliJ SDK version to fix it. It is possible to acquire such sources by switching the project to IC-2021.2.3 or browsing the IntelliJ Community sources located on GitHub. Note that it is possible to change the repository to a specific tag, like 213.

Wrapping up

The IntelliJ Platform SDK is constantly updated simultaneously with new and existing JetBrains IDEs. Staying up to date with the API to ensure everything works correctly is vital to providing a high-quality product to end-users. Taking that into account when migrating from an unsupported solution for building plugins, like the DevKit-based configuration, to the Gradle build system is paramount. Along with IDEs, tooling for the IntelliJ Platform is also updated constantly to provide the best development experience to both JetBrains and third-party developers.

If you get stuck at any point, please feel free to reach out to us using the IntelliJ IDEA Open API and Plugin Development Forum or JetBrains Platform Slack.

To stay up to date regarding the IntelliJ Platform and JetBrains Marketplace, follow us on Twitter!
Jakub Chrzanowski & JetBrains Platform

image description