Skip to content

Commit

Permalink
Add ApolloCompilerPluginProvider (#5865)
Browse files Browse the repository at this point in the history
* Add logger and arguments

* small tweak to the KDoc

* improve migration guide

* more KDoc

* Update libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/ApolloCompilerPluginEnvironment.kt

Co-authored-by: Benoit 'BoD' Lubek <BoD@JRAF.org>

* Update libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo3/compiler/ApolloCompilerPluginEnvironment.kt

Co-authored-by: Benoit 'BoD' Lubek <BoD@JRAF.org>

* remove exception, messager and add comment

---------

Co-authored-by: Benoit 'BoD' Lubek <BoD@JRAF.org>
  • Loading branch information
martinbonnin and BoD committed Apr 30, 2024
1 parent 4e70750 commit 8efd9c1
Show file tree
Hide file tree
Showing 23 changed files with 340 additions and 50 deletions.
38 changes: 38 additions & 0 deletions docs/source/migration/4.0.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,46 @@ If you are using `packageNamesFromFilePaths` and `schemaFile`, you'll need to us
```

<Note>

Apollo Kotlin 3 was using the operation root directories to compute the schema normalized path which could be wrong in some edge cases. Using fileTree ensures the normalized path is consistent.
</Note>

### Migrating to ApolloCompilerPlugin

4.0 introduces `ApolloCompilerPlugin` as a way to customize code generation. `ApolloCompilerPlugin` are loaded using the [ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) API and run in a separate classloader from your Gradle build. As a result, using `Service.operationIdGenerator`/`Service.operationOutputGenerator` together with `ApolloCompilerPlugin` is not possible.

`Service.operationIdGenerator`/`Service.operationOutputGenerator` are deprecated and will be removed in a future release. You can migrate to `ApolloCompilerPlugin` using the instructions [from the dedicated page](https://www.apollographql.com/docs/kotlin/v4/advanced/compiler-plugins) and the `ApolloCompilerPlugin.operationIds()` method:

```kotlin
// Replace (in your build scripts)
val operationOutputGenerator = object: OperationOutputGenerator {
override fun generate(operationDescriptorList: Collection<OperationDescriptor>): OperationOutput {
return operationDescriptorList.associateBy {
it.source.sha1()
}
}
override val version: String = "v1"
}

// Or, if using OperationIdGenerator, replace
val operationIdGenerator = object: OperationIdGenerator {
override fun apply(operationDocument: String, operationName: String): String {
return operationDocument.sha1()
}

override val version: String = "v1"
}

// With (in your build compiler plugin module)
class MyPlugin: ApolloCompilerPlugin {
override fun operationIds(descriptors: List<OperationDescriptor>): List<OperationId>? {
return descriptors.map {
OperationId(it.source.sha1(), it.name)
}
}
}
```

### Misc

* Publishing is no longer configured automatically.
Expand Down Expand Up @@ -526,6 +563,7 @@ apolloClient.query().rxSingle()
apolloClient.query().toFlow().asFlowable().firstOrError()
```


## Example of a migration

If you are looking for inspiration, we updated the [3.x integration tests to use 4.0](https://github.com/apollographql/apollo-kotlin/pull/5418/). If you have an open source project that migrated, feel free to share it and we'll include it here.
Expand Down
3 changes: 3 additions & 0 deletions libraries/apollo-compiler/api/apollo-compiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,9 @@ public final class com/apollographql/apollo3/compiler/codegen/kotlin/helpers/Add
public static final fun addInternal (Lcom/squareup/kotlinpoet/FileSpec$Builder;Ljava/util/List;)Lcom/squareup/kotlinpoet/FileSpec$Builder;
}

public final class com/apollographql/apollo3/compiler/internal/GradleCompilerPluginLogger$Companion {
}

public final class com/apollographql/apollo3/compiler/ir/IrAccessor$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.apollographql.apollo3.compiler

import com.apollographql.apollo3.annotations.ApolloExperimental

/**
* [ApolloCompilerPluginEnvironment] contains the environment where the Apollo compiler is run.
*/
@ApolloExperimental
class ApolloCompilerPluginEnvironment(
/**
* Arguments as passed from the Gradle plugin
*/
val arguments: Map<String, Any?>,
/**
* A logger that can be used by the plugin.
*/
val logger: ApolloCompilerPluginLogger,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.apollographql.apollo3.compiler

import com.apollographql.apollo3.annotations.ApolloExperimental

/**
* [ApolloCompilerPluginLogger] allows logging from the context of the Apollo compiler.
*
* Typically, the Apollo compiler is run from an isolated classloader and cannot use the Gradle logging functions but can respect the logging level set by the user.
*/
@ApolloExperimental
interface ApolloCompilerPluginLogger {
fun logging(message: String)
fun info(message: String)
fun warn(message: String)
fun error(message: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.apollographql.apollo3.compiler

import com.apollographql.apollo3.annotations.ApolloExperimental

/**
* [ApolloCompilerPluginProvider] is entry point for creating [ApolloCompilerPlugin].
*
* [ApolloCompilerPluginProvider] is created by [java.util.ServiceLoader], make sure to include a matching `META-INF/services` resource.
*/
@ApolloExperimental
fun interface ApolloCompilerPluginProvider {
/**
* Creates the [ApolloCompilerPlugin]
*/
fun create(environment: ApolloCompilerPluginEnvironment): ApolloCompilerPlugin
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.apollographql.apollo3.compiler.internal

import com.apollographql.apollo3.annotations.ApolloInternal
import com.apollographql.apollo3.compiler.ApolloCompilerPluginLogger

@ApolloInternal
class GradleCompilerPluginLogger(val loglevel: Int) : ApolloCompilerPluginLogger {

override fun logging(message: String) {
if (loglevel <= LOGGING_LEVEL_LOGGING)
println("v: [apollo] $message")
}

override fun info(message: String) {
if (loglevel <= LOGGING_LEVEL_INFO)
println("i: [apollo] $message")
}

override fun warn(message: String) {
if (loglevel <= LOGGING_LEVEL_WARN)
println("w: [apollo] $message")
}

override fun error(message: String) {
if (loglevel <= LOGGING_LEVEL_ERROR)
println("e: [apollo] $message")
}

companion object {
/**
* Matches Gradle LogLevel
* See https://github.com/gradle/gradle/blob/71f42531a742bc263c61f1d0dc21bb6570cc817b/platforms/core-runtime/logging-api/src/main/java/org/gradle/api/logging/LogLevel.java#L21
*/
const val LOGGING_LEVEL_LOGGING = 0
const val LOGGING_LEVEL_INFO = 1
const val LOGGING_LEVEL_WARN = 3
const val LOGGING_LEVEL_ERROR = 5
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class OperationDescriptor(
*/
val name: String,
/**
* The source of the operation as it is sent over the wire, including fragments
* The source of the operation document as it is sent over the wire, including fragments
*/
val source: String,
/**
Expand All @@ -30,6 +30,14 @@ class OperationDescriptor(

/**
* The id of an operation associated with its name so that it can be looked up.
*
* @param id the generated id for the operation
* @param name the name of the operation, such as "FooQuery" below
* ```graphql
* query FooQuery {
* foo
* }
* ```
*/
class OperationId(val id: String, val name: String)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.apollographql.apollo3.gradle.api

import com.apollographql.apollo3.annotations.ApolloExperimental

@ApolloExperimental
interface CompilerPlugin {
/**
* Adds the given argument to the [com.apollographql.apollo3.compiler.ApolloCompilerPlugin].
* If two arguments are added with the same name, the second one overwrites the first one.
*
* @param name the name of the argument
* @param value the value of the argument. One of:
* - [String]
* - [Int]
* - [Double]
* - [Boolean]
* - [List]
* - [Map]
*/
fun argument(name: String, value: Any)
}
Original file line number Diff line number Diff line change
Expand Up @@ -907,4 +907,7 @@ interface Service {
}

fun plugin(dependencyNotation: Any)

@ApolloExperimental
fun plugin(dependencyNotation: Any, block: Action<CompilerPlugin>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import com.apollographql.apollo3.compiler.toIrOperations
import com.apollographql.apollo3.compiler.toIrOptions
import com.apollographql.apollo3.compiler.writeTo
import com.apollographql.apollo3.gradle.internal.ApolloGenerateSourcesFromIrTask.Companion.findCodegenSchemaFile
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
Expand All @@ -22,7 +20,7 @@ import org.gradle.workers.WorkerExecutor
import java.io.File
import javax.inject.Inject

abstract class ApolloGenerateIrOperationsTask: DefaultTask() {
abstract class ApolloGenerateIrOperationsTask: ApolloTaskWithClasspath() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val codegenSchemaFiles: ConfigurableFileCollection
Expand All @@ -42,9 +40,6 @@ abstract class ApolloGenerateIrOperationsTask: DefaultTask() {
@get:OutputFile
abstract val irOperationsFile: RegularFileProperty

@get:Classpath
abstract val classpath: ConfigurableFileCollection

@Inject
abstract fun getWorkerExecutor(): WorkerExecutor

Expand All @@ -61,6 +56,8 @@ abstract class ApolloGenerateIrOperationsTask: DefaultTask() {
it.upstreamIrFiles = upstreamIrFiles.isolate()
it.irOptionsFile.set(irOptionsFile)
it.irOperationsFile.set(irOperationsFile)
it.arguments = arguments.get()
it.logLevel = logLevel.get().ordinal
}
}
}
Expand All @@ -69,7 +66,7 @@ private abstract class GenerateIrOperations : WorkAction<GenerateIrOperationsPar
override fun execute() {
with(parameters) {
val upstreamIrOperations = upstreamIrFiles.toInputFiles().map { it.file.toIrOperations() }
val plugin = apolloCompilerPlugin()
val plugin = apolloCompilerPlugin(arguments, logLevel)

ApolloCompiler.buildIrOperations(
executableFiles = graphqlFiles.toInputFiles(),
Expand All @@ -89,4 +86,6 @@ private interface GenerateIrOperationsParameters : WorkParameters {
var upstreamIrFiles: List<Pair<String, File>>
val irOptionsFile: RegularFileProperty
val irOperationsFile: RegularFileProperty
var arguments: Map<String, Any?>
var logLevel: Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@ import com.apollographql.apollo3.compiler.OperationOutputGenerator
import com.apollographql.apollo3.compiler.PackageNameGenerator
import com.apollographql.apollo3.compiler.codegen.SchemaAndOperationsLayout
import com.apollographql.apollo3.compiler.toCodegenOptions
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
Expand All @@ -26,7 +22,7 @@ import org.gradle.api.tasks.PathSensitivity
import org.gradle.workers.WorkerExecutor
import javax.inject.Inject

abstract class ApolloGenerateSourcesBaseTask : DefaultTask() {
abstract class ApolloGenerateSourcesBaseTask : ApolloTaskWithClasspath() {
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val codegenOptionsFile: RegularFileProperty
Expand All @@ -50,12 +46,6 @@ abstract class ApolloGenerateSourcesBaseTask : DefaultTask() {
@get:OutputDirectory
abstract val outputDir: DirectoryProperty

@get:Classpath
abstract val classpath: ConfigurableFileCollection

@get:Input
abstract val hasPlugin: Property<Boolean>

@Inject
abstract fun getWorkerExecutor(): WorkerExecutor
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ abstract class ApolloGenerateSourcesFromIrTask : ApolloGenerateSourcesBaseTask()
it.outputDir.set(outputDir)
it.metadataOutputFile.set(metadataOutputFile)
it.hasPlugin = hasPlugin.get()
it.arguments = arguments.get()
it.logLevel = logLevel.get().ordinal
}
}
}
Expand All @@ -98,7 +100,11 @@ private abstract class GenerateSourcesFromIr : WorkAction<GenerateSourcesFromIrP
val codegenSchemaFile = codegenSchemas.findCodegenSchemaFile()

val codegenSchema = codegenSchemaFile.toCodegenSchema()
val plugin = apolloCompilerPlugin(hasPlugin)
val plugin = apolloCompilerPlugin(
arguments,
logLevel,
hasPlugin
)

ApolloCompiler.buildSchemaAndOperationsSourcesFromIr(
codegenSchema = codegenSchema,
Expand Down Expand Up @@ -127,5 +133,7 @@ private interface GenerateSourcesFromIrParameters : WorkParameters {
val operationManifestFile: RegularFileProperty
val outputDir: DirectoryProperty
val metadataOutputFile: RegularFileProperty
var arguments: Map<String, Any?>
var logLevel: Int
}

Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ abstract class ApolloGenerateSourcesTask : ApolloGenerateSourcesBaseTask() {
it.codegenOptions.set(codegenOptionsFile)
it.operationManifestFile.set(operationManifestFile)
it.outputDir.set(outputDir)
it.arguments = arguments.get()
it.logLevel = logLevel.get().ordinal
}
}
}
Expand All @@ -88,7 +90,11 @@ private abstract class GenerateSources : WorkAction<GenerateSourcesParameters> {
with(parameters) {
val schemaInputFiles = (schemaFiles.takeIf { it.isNotEmpty() } ?: fallbackSchemaFiles).toInputFiles()
val executableInputFiles = graphqlFiles.toInputFiles()
val plugin = apolloCompilerPlugin(hasPlugin)
val plugin = apolloCompilerPlugin(
arguments,
logLevel,
hasPlugin
)

ApolloCompiler.buildSchemaAndOperationsSources(
schemaFiles = schemaInputFiles,
Expand Down Expand Up @@ -123,4 +129,6 @@ private interface GenerateSourcesParameters : WorkParameters {
val irOptions: RegularFileProperty
val operationManifestFile: RegularFileProperty
val outputDir: DirectoryProperty
var arguments: Map<String, Any?>
var logLevel: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.apollographql.apollo3.gradle.internal

import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input

abstract class ApolloTaskWithClasspath: DefaultTask() {
@get:Classpath
abstract val classpath: ConfigurableFileCollection

@get:Input
abstract val hasPlugin: Property<Boolean>

@get:Input
abstract val arguments: MapProperty<String, Any?>

@get:Input
abstract val logLevel: Property<LogLevel>

class Options(
val classpath: FileCollection,
val hasPlugin: Boolean,
val arguments: Map<String, Any?>,
val logLevel: LogLevel
)
}

0 comments on commit 8efd9c1

Please sign in to comment.