Writing Your Second Kotlin Compiler Plugin, Part 1 — Project Setup

Image for post
Image for post
Photo by Ben Stern on Unsplash

At the time of writing this article, Kotlin compatibility for IR backend is in Alpha status and the compiler plugin API is Experimental. As such, information contained in this article about IR and compiler plugins could be out-of-date or incorrect. If official documentation exists, please refer to it first.

I’ve been wanting to write this series of blog posts for a while. Inspired By Kevin Most’s 2018 KotlinConf talk — Writing Your First Kotlin Compiler Plugin — I wanted to show how we could write our second compiler plugin, something IR based that works on all Kotlin targets.

In this first part we will go over the project setup required to build a compiler plugin. In later parts we will explore navigating and transforming Kotlin IR.

Setup

  • Gradle plugin which adds your plugin to Kotlin compiler classpath,
  • Kotlin compiler plugin for non-native Kotlin platforms,
  • Kotlin compiler plugin for Kotlin/Native platform,

Kotlin/Native requires a separate compiler plugin because certain classes used by the Kotlin compiler have a different package on Kotlin/Native. We’ll explore this oddity more in-depth later.

Gradle Plugin

  • Artifact coordinates of the Kotlin compiler plugin,
  • ID string of the Kotlin compiler plugin,
  • Translation of Gradle configuration to command line options.

The artifact coordinates are used to download the correct compiler plugin artifact from MavenCentral (or other location) to include on the compiler plugin classpath. The compiler plugin ID string is used to separate command line options by plugin to avoid conflicting options between multiple plugins.

The artifact coordinates and ID we’ll define with Gradle using the buildconfig plugin. Since the artifact coordinates — group, name, and version — are already defined by Gradle and the Kotlin compiler plugin ID will be the Gradle plugin ID, this will make things consistent when everything is published.

These buildconfig values are used by a KotlinCompilerPluginSupportPlugin implementation which is responsible for bridging between Gradle and the Kotlin compiler.

Any custom Gradle properties defined in a Gradle extension are translated to Kotlin compiler arguments using the applyToCompilation function. These will be read by our Kotlin compiler plugin which is next!

Kotlin Plugin

The CommandLineProcessor is pretty easy to understand: It defines the Kotlin compiler plugin ID string (same as in Gradle plugin) and expected command line options. The processor is also responsible for parsing the command line options of the plugin and placing them in a CompilerConfiguration. It should read and process the same values defined by the Gradle plugin.

The ComponentRegistrar is where the actual work of a compiler plugin actually begins. The registrar is responsible for registering extension components with the project being compiled. There are a number of extension points currently available in the Kotlin compiler, but the one we will be focusing on is IrGenerationExtension, which allows for navigating and transforming Kotlin IR.

An instance of IrGenerationExtension can be created and registered within a ComponentRegistrar. This instance will be called during compilation when IR code needs to be generated (or transformed). The entry point for this extension provides an IrModuleFragment and IrPluginContext which provide everything we need to navigate and transform the module IR code.

We’ll be going more into how to use the provided IrModuleFragment and IrPluginContext values in the next parts of this series. But next we need to talk about how Kotlin/Native is different.

Kotlin Plugin — Native

The only difference I have found between these artifacts is the embedded artifact shadows some classes so their package is different. Other then that, the code between these plugins can be exactly the same. To share code, I use Gradle to copy the code of the non-Kotlin/Native compiler plugin over to the Kotlin/Native compiler plugin module and change the import statements as required.

This isn’t ideal, but I’m sure this oddity will be resolved as Kotlin/Native and compiler plugins mature.

Kotlin Plugin — Testing

If you execute the tests you should see the following output present in the log.

i: Argument 'string' = Hello, World!
i: Argument 'file' = file.txt

And with that, congrats! You now know how to set up a Kotlin compiler plugin project and are ready to start navigating and transforming IR code! If you want to avoid the manual work of setting all of this up, you can use the GitHub template I created when writing this article.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store