Apollo compiler plugins


Compiler plugins are currently experimental in Apollo Kotlin. If you have feedback on them, please let us know via GitHub issues or in the Kotlin Slack community.

The Apollo compiler supports a wide range of options. For the cases where these options are not enough, you can use Apollo compiler plugins to modify the behaviour of the compiler.

Apollo compiler plugins allow to:

  • Change the layout of the generated sources (name of the classes, package names, capitalization rules).

  • Change the ids of operation for persisted queries.

  • Transform the JavaPoet/KotlinPoet models.

  • Transform the Apollo IR.

Implementing a compiler plugin

In this example we will implement a plugin that uses custom persisted queries ids registered on your backend.

The Apollo compiler use the ServiceLoader API to load plugins at runtime. Plugins need to be implemented in a separate module that is added to the classpath.

To start, create a new Gradle module and add apollo-compiler as a dependency to the module build.gradle[.kts] file. In this example, we'll use apollo-compiler-plugin as module name:

Kotlin
1// apollo-compiler-plugin/build.gradle.kts
2plugins {
3  id("org.jetbrains.kotlin.jvm")
4}
5
6dependencies {
7  // Add apollo-compiler as a dependency
8  implementation("com.apollographql.apollo:apollo-compiler:4.0.1")  
9}

Next create your plugin in a src/main/kotlin/mypackage/MyPlugin file:

Kotlin
1package mypackage
2
3import com.apollographql.apollo.compiler.OperationOutputGenerator
4import com.apollographql.apollo.compiler.ApolloCompilerPlugin
5import com.apollographql.apollo.compiler.operationoutput.OperationDescriptor
6import com.apollographql.apollo.compiler.operationoutput.OperationId
7
8class MyPlugin: ApolloCompilerPlugin {
9  override fun operationIds(descriptors: List<OperationDescriptor>): List<OperationId> {
10    // This assumes the returned ids are in the same order as the descriptors
11    return registerOperations(descriptors).withIndex().map { OperationId(it.value, descriptors[it.index].name) }
12  }
13
14  /**
15   * Send operations to a remote server and return the server persisted ids 
16   */
17  fun registerOperations(descriptors: List<OperationDescriptor>): List<String> {
18    // ...
19  }
20}

Next, create an ApolloCompilerPluginProvider. This is the entry point of compiler plugins. It is loaded using the ServiceLoader API.

Kotlin
1class MyPluginProvider: ApolloCompilerPluginProvider {
2  override fun create(environment: ApolloCompilerPluginEnvironment): ApolloCompilerPlugin {
3    return MyPlugin()
4  }
5}

Make your plugin discoverable by ServiceLoader by adding a resource in src/main/resources/META-INF/services/com.apollographql.apollo.compiler.ApolloCompilerPluginProvider. This file contains the fully qualified name of your plugin:

Text
1mypackage.MyPluginProvider
 note
The name of the resource file is important. It must be com.apollographql.apollo.compiler.ApolloCompilerPluginProvider and be in the META-INF/services folder. This is how ServiceLoader looks up plugins at runtime.
 note
Only a single plugin is supported at this time. If you need more, you can usually have a single wrapper plugin that calls your different implementations in the required order.

Adding a plugin to the Apollo compiler classpath

Use the Service.plugin() Gradle method to add the plugin to the Apollo compiler classpath:

Kotlin
1// app/build.gradle.kts
2plugins {
3  id("org.jetbrains.kotlin.jvm")
4  id("com.apollographql.apollo")
5}
6
7apollo {
8  service("service") {
9    packageName.set("com.example")
10    
11    // Add your plugin to the Apollo compiler classpath
12    plugin(project(":apollo-compiler-plugin"))
13  }
14}

The plugin code will now be invoked the next time the compiler is invoked.

Passing arguments to your Apollo compiler plugin

Because the compiler plugin runs in an isolated classpath, you can't use classes or data from your main build logic classpath.

In order to pass build-time arguments to your Apollo compiler plugin, use the argument() function:

Kotlin
1apollo {
2  service("service") {
3    packageName.set("com.example")
4    
5    // Add your plugin to the Apollo compiler classpath
6    plugin(project(":apollo-compiler-plugin")) {
7      argument("token", token)
8    }
9  }
10}

The arguments are available in ApolloCompilerPluginEnvironment.arguments:

Kotlin
1class MyPluginProvider: ApolloCompilerPluginProvider {
2  override fun create(environment: ApolloCompilerPluginEnvironment): ApolloCompilerPlugin {
3    return MyPlugin(environment.arguments.get("token") as String)
4  }
5}

Arguments must be serializable and be instances of classes accessible from the bootstrap classloader. In practice, built-in types and collections are supported.

Limitations

Because codegen is run in a separate classloader when using compiler plugins, it's not possible to use packageNameGenerator, operationIdGenerator or operationOutputGenerator at the same time as compiler plugins. If you want to use them, you'll have to:

  • use ApolloCompilerPlugin.layout() instead of packageNameGenerator

  • use ApolloCompilerPlugin.operationIds() instead of operationIdGenerator and operationOutputGenerator

Other references

For other plugin APIs like layout, IR, JavaPoet and KotlinPoet transforms, check out the ApolloCompilerPlugin API docs

For more examples, check out the integration-tests.

Feedback

Edit on GitHub

Forums