Skip to content

[4.x] prepare compiler plugins for 4.3 #6549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions libraries/apollo-compiler/api/apollo-compiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,20 @@ public abstract interface class com/apollographql/apollo/compiler/ApolloCompiler

public abstract interface class com/apollographql/apollo/compiler/ApolloCompilerRegistry {
public abstract fun registerExecutableDocumentTransform (Ljava/lang/String;[Lcom/apollographql/apollo/compiler/Order;Lcom/apollographql/apollo/compiler/ExecutableDocumentTransform;)V
public abstract fun registerExtraCodeGenerator (Lcom/apollographql/apollo/compiler/CodeGenerator;)V
public abstract fun registerForeignSchemas (Ljava/util/List;)V
public abstract fun registerIrTransform (Ljava/lang/String;[Lcom/apollographql/apollo/compiler/Order;Lcom/apollographql/apollo/compiler/Transform;)V
public abstract fun registerJavaOutputTransform (Ljava/lang/String;[Lcom/apollographql/apollo/compiler/Order;Lcom/apollographql/apollo/compiler/Transform;)V
public abstract fun registerKotlinOutputTransform (Ljava/lang/String;[Lcom/apollographql/apollo/compiler/Order;Lcom/apollographql/apollo/compiler/Transform;)V
public abstract fun registerLayout (Lcom/apollographql/apollo/compiler/LayoutFactory;)V
public abstract fun registerOperationIdsGenerator (Lcom/apollographql/apollo/compiler/OperationIdsGenerator;)V
public abstract fun registerSchemaCodeGenerator (Lcom/apollographql/apollo/compiler/SchemaCodeGenerator;)V
}

public final class com/apollographql/apollo/compiler/Before : com/apollographql/apollo/compiler/Order {
public fun <init> (Ljava/lang/String;)V
public final fun getId ()Ljava/lang/String;
}

public abstract interface class com/apollographql/apollo/compiler/CodeGenerator {
public abstract fun generate (Lcom/apollographql/apollo/ast/GQLDocument;Ljava/io/File;)V
}

public final class com/apollographql/apollo/compiler/CodegenMetadata {
public static final field Companion Lcom/apollographql/apollo/compiler/CodegenMetadata$Companion;
public fun <init> (Ljava/lang/String;Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
Expand Down Expand Up @@ -389,7 +385,7 @@ public final class com/apollographql/apollo/compiler/OperationIdGenerator$Sha256
}

public abstract interface class com/apollographql/apollo/compiler/OperationIdsGenerator {
public abstract fun generate (Ljava/util/Collection;)Ljava/util/List;
public abstract fun generate (Ljava/util/List;)Ljava/util/List;
}

public abstract interface class com/apollographql/apollo/compiler/OperationOutputGenerator {
Expand Down Expand Up @@ -452,6 +448,10 @@ public final class com/apollographql/apollo/compiler/RuntimeAdapterInitializer :
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public abstract interface class com/apollographql/apollo/compiler/SchemaCodeGenerator {
public abstract fun generate (Lcom/apollographql/apollo/ast/GQLDocument;Ljava/io/File;)V
}

public abstract interface class com/apollographql/apollo/compiler/SchemaCodegenOpt {
public abstract fun getGenerateSchema ()Ljava/lang/Boolean;
public abstract fun getGeneratedSchemaName ()Ljava/lang/String;
Expand Down
3 changes: 2 additions & 1 deletion libraries/apollo-compiler/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ abstract class GeneratePluginVersion : DefaultTask() {
versionFile.parentFile.mkdirs()
versionFile.writeText("""// Generated file. Do not edit!
package com.apollographql.apollo.compiler
const val APOLLO_VERSION = "${version.get()}"
@JvmField
val APOLLO_VERSION = "${version.get()}"
""")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.apollographql.apollo.ast.DifferentShape
import com.apollographql.apollo.ast.DirectiveRedefinition
import com.apollographql.apollo.ast.ForeignSchema
import com.apollographql.apollo.ast.GQLDefinition
import com.apollographql.apollo.ast.GQLDirective
import com.apollographql.apollo.ast.GQLDocument
import com.apollographql.apollo.ast.GQLFragmentDefinition
import com.apollographql.apollo.ast.GQLOperationDefinition
Expand All @@ -15,7 +14,6 @@ import com.apollographql.apollo.ast.IncompatibleDefinition
import com.apollographql.apollo.ast.Issue
import com.apollographql.apollo.ast.ParserOptions
import com.apollographql.apollo.ast.QueryDocumentMinifier
import com.apollographql.apollo.ast.Schema
import com.apollographql.apollo.ast.UnknownDirective
import com.apollographql.apollo.ast.UnusedFragment
import com.apollographql.apollo.ast.UnusedVariable
Expand All @@ -37,7 +35,7 @@ import com.apollographql.apollo.compiler.codegen.kotlin.KotlinCodegen
import com.apollographql.apollo.compiler.codegen.kotlin.KotlinOutput
import com.apollographql.apollo.compiler.codegen.kotlin.toSourceOutput
import com.apollographql.apollo.compiler.codegen.plus
import com.apollographql.apollo.compiler.internal.addRequiredFields
import com.apollographql.apollo.compiler.internal.ApolloExecutableDocumentTransform
import com.apollographql.apollo.compiler.internal.checkApolloInlineFragmentsHaveTypeCondition
import com.apollographql.apollo.compiler.internal.checkApolloReservedEnumValueNames
import com.apollographql.apollo.compiler.internal.checkApolloTargetNameClashes
Expand Down Expand Up @@ -236,7 +234,6 @@ object ApolloCompiler {
val warnOnDeprecatedUsages = options.warnOnDeprecatedUsages ?: defaultWarnOnDeprecatedUsages
val failOnWarnings = options.failOnWarnings ?: defaultFailOnWarnings
val fieldsOnDisjointTypesMustMerge = options.fieldsOnDisjointTypesMustMerge ?: defaultFieldsOnDisjointTypesMustMerge
val addTypename = options.addTypename ?: defaultAddTypename
val generateOptionalOperationVariables = options.generateOptionalOperationVariables ?: defaultGenerateOptionalOperationVariables
val alwaysGenerateTypesMatching = options.alwaysGenerateTypesMatching ?: defaultAlwaysGenerateTypesMatching

Expand Down Expand Up @@ -264,45 +261,54 @@ object ApolloCompiler {
/**
* Step 3, Modify the AST to add typename and key fields
*/
val fragmentDefinitions = (definitions.filterIsInstance<GQLFragmentDefinition>() + upstreamFragmentDefinitions).associateBy { it.name }
var fragments = definitions.filterIsInstance<GQLFragmentDefinition>().map {
addRequiredFields(it, addTypename, schema, fragmentDefinitions)
val hasCacheCompilerPlugin = try {
/**
* We have the cache compiler plugin in the class path. Do not do the work twice
*/
Class.forName("com.apollographql.cache.apollocompilerplugin.ApolloCacheCompilerPlugin")
true
} catch (_: ClassNotFoundException) {
false
}

var operations = definitions.filterIsInstance<GQLOperationDefinition>().map {
var operation = addRequiredFields(it, addTypename, schema, fragmentDefinitions)
if (schema.directiveDefinitions.containsKey(Schema.DISABLE_ERROR_PROPAGATION)
&& schema.schemaDefinition?.directives?.any { schema.originalDirectiveName(it.name) == Schema.CATCH_BY_DEFAULT } == true) {
operation = operation.copy(
directives = operation.directives + GQLDirective(null, Schema.DISABLE_ERROR_PROPAGATION, emptyList())
)
}
operation
}
var document = ApolloExecutableDocumentTransform(options.addTypename ?: defaultAddTypename, !hasCacheCompilerPlugin).transform(
schema = schema,
document = GQLDocument(definitions, sourceLocation = null),
upstreamFragmentDefinitions
)

if (documentTransform != null) {
val transformedDocument = documentTransform.transform(schema, GQLDocument(sourceLocation = null, definitions = fragments + operations), upstreamFragmentDefinitions)
fragments = transformedDocument.definitions.filterIsInstance<GQLFragmentDefinition>()
operations = transformedDocument.definitions.filterIsInstance<GQLOperationDefinition>()
document = documentTransform.transform(schema, document, upstreamFragmentDefinitions)
}

// Remember the fragments with the possibly updated fragments
val allFragmentDefinitions = (fragments + upstreamFragmentDefinitions).associateBy { it.name }
val allFragmentDefinitions = (document.definitions.filterIsInstance<GQLFragmentDefinition>() + upstreamFragmentDefinitions).associateBy { it.name }

// Check if all the key fields are present in operations and fragments
// (do this only if there are key fields as it may be costly)
if (schema.hasTypeWithTypePolicy()) {
operations.forEach {
checkKeyFields(it, schema, allFragmentDefinitions)
}
fragments.forEach {
checkKeyFields(it, schema, allFragmentDefinitions)
document.definitions.forEach {
when(it) {
is GQLOperationDefinition -> checkKeyFields(it, schema, allFragmentDefinitions)
is GQLFragmentDefinition -> checkKeyFields(it, schema, allFragmentDefinitions)
else -> Unit
}
}
}

/**
* Build the IR
*/
val operations = mutableListOf<GQLOperationDefinition>()
val fragments = mutableListOf<GQLFragmentDefinition>()
document.definitions.forEach {
when(it) {
is GQLOperationDefinition -> operations.add(it)
is GQLFragmentDefinition -> fragments.add(it)
else -> Unit
}
}

return IrOperationsBuilder(
schema = schema,
operationDefinitions = operations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ import com.apollographql.apollo.compiler.ir.IrOperations
import com.apollographql.apollo.compiler.operationoutput.OperationDescriptor
import com.apollographql.apollo.compiler.operationoutput.OperationId
import java.io.File
import kotlin.jvm.Throws

/**
* [ApolloCompilerPlugin] allows to customize the behavior of the Apollo Compiler.
*
* [ApolloCompilerPlugin] may be instantiated several times in a codegen run. Each instance is create in a
* separate classloader.
* The classloaders contains `apollo-compiler` classes and the runtime classpath of the [ApolloCompilerPlugin].
* You may throw from [ApolloCompilerPlugin] methods to fail the build.
* [ApolloCompilerPlugin] instances are created by [java.util.ServiceLoader] and may be instantiated several times in a codegen run.
* Each instance is created in a separate classloader and contains the `apollo-compiler` version loaded by your build tool.
*
* You may check [com.apollographql.apollo.compiler.APOLLO_VERSION] to check for compatibility of your
* plugin with the version of `apollo-compiler` available at runtime.
*/
interface ApolloCompilerPlugin {
/**
Expand All @@ -34,7 +36,6 @@ interface ApolloCompilerPlugin {
* @param registry the registry where to register transformations.
*/
fun beforeCompilationStep(environment: ApolloCompilerPluginEnvironment, registry: ApolloCompilerRegistry) {

}

/**
Expand All @@ -61,51 +62,65 @@ interface ApolloCompilerPlugin {
/**
* @return the [Transform] to be applied to [JavaOutput] or null to use the default [Transform]
*/
@Deprecated("Call `registry.registerJavaOutputTransform()` from beforeCompilationStep() instead.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
fun javaOutputTransform(): Transform<JavaOutput>? {
return null
}

/**
* @return the [Transform] to be applied to [KotlinOutput] or null to use the default [Transform]
*/
@Deprecated("Call `registry.registerKotlinOutputTransform()` from beforeCompilationStep() instead.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
fun kotlinOutputTransform(): Transform<KotlinOutput>? {
return null
}

/**
* @return a [DocumentTransform] to transform operations and/or fragments
*/
@ApolloExperimental
@Suppress("DEPRECATION")
@Deprecated("Call `registry.registerExecutableDocumentTransform()` from beforeCompilationStep() instead.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
fun documentTransform(): DocumentTransform? {
return null
}

/**
* @return the [Transform] to be applied to [IrOperations] or null to use the default [Transform]
*/
@ApolloExperimental
/**
* @return a [DocumentTransform] to transform operations and/or fragments
*/
@Deprecated("Call `registry.registerIrTransform()` from beforeCompilationStep() instead.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
fun irOperationsTransform(): Transform<IrOperations>? {
return null
}

/**
* @return A list of [ForeignSchema] supported by this plugin
*/
@ApolloExperimental
@Deprecated("Call `registry.registerForeignSchemas()` from beforeCompilationStep() instead.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
fun foreignSchemas(): List<ForeignSchema> {
return emptyList()
}

/**
* @return A [SchemaListener] called whenever the schema changed
*/
@ApolloExperimental
@Suppress("DEPRECATION")
@Deprecated("Call `registry.registerSchemaCodeGenerator()` from beforeCompilationStep() instead.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
fun schemaListener(): SchemaListener? {
return null
}
}

@ApolloExperimental
@Deprecated("Use CodeGenerator instead.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
interface SchemaListener {
/**
* Called when the schema changed and codegen needs to be updated
Expand All @@ -121,7 +136,8 @@ interface SchemaListener {
/**
* A [DocumentTransform] transforms operations and fragments at build time. [DocumentTransform] can add or remove fields automatically for an example.
*/
@ApolloExperimental
@Deprecated("Use ExecutableDocumentTransform instead.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
interface DocumentTransform {
/**
* Transforms the given operation.
Expand Down Expand Up @@ -154,6 +170,7 @@ interface Transform<T> {
/**
* A [ExecutableDocumentTransform] transforms operations and fragments at build time. [ExecutableDocumentTransform] can add or remove fields automatically, for an example.
*/
@ApolloExperimental
fun interface ExecutableDocumentTransform {
/**
* Transforms the given document.
Expand All @@ -168,43 +185,68 @@ fun interface ExecutableDocumentTransform {
fun transform(schema: Schema, document: GQLDocument, extraFragmentDefinitions: List<GQLFragmentDefinition>): GQLDocument
}

/**
* An [OperationIdsGenerator] is responsible for computing [OperationId] from [OperationDescriptor].
*
* This is used for [persisted queries](https://www.apollographql.com/docs/kotlin/advanced/persisted-queries).
*/
@ApolloExperimental
fun interface OperationIdsGenerator {
fun generate(operationDescriptorList: Collection<OperationDescriptor>): List<OperationId>
/**
* Generate the [OperationId]s from [operationDescriptors].
* Implementations my throw to fail the build.
*/
@Throws
fun generate(operationDescriptors: List<OperationDescriptor>): List<OperationId>
}

@ApolloExperimental
sealed interface Order

@ApolloExperimental
class Before(val id: String): Order

@ApolloExperimental
class After(val id: String): Order

/**
* A code generator that may write extra schema code in addition to what the Apollo Kotlin compiler is generating.
*/
@ApolloExperimental
fun interface SchemaCodeGenerator {
/**
* @param schema a [GQLDocument] representing the current schema.
* @param outputDirectory the directory where to write source files.
*/
fun generate(schema: GQLDocument, outputDirectory: File)
}

interface ApolloCompilerRegistry {
/**
* Registers an [OperationIdsGenerator].
*
* Use this function implement [persisted queries](https://www.apollographql.com/docs/kotlin/advanced/persisted-queries).
*/
fun registerOperationIdsGenerator(generator: OperationIdsGenerator)

@ApolloExperimental
fun registerForeignSchemas(schemas: List<ForeignSchema>)

@ApolloExperimental
fun registerExecutableDocumentTransform(id: String, vararg orders: Order, transform: ExecutableDocumentTransform)

@ApolloExperimental
fun registerIrTransform(id: String, vararg orders: Order, transform: Transform<IrOperations>)

@ApolloExperimental
fun registerLayout(factory: LayoutFactory)
fun registerOperationIdsGenerator(generator: OperationIdsGenerator)

@ApolloExperimental
fun registerJavaOutputTransform(id: String, vararg orders: Order, transform: Transform<JavaOutput>)

@ApolloExperimental
fun registerKotlinOutputTransform(id: String, vararg orders: Order, transform: Transform<KotlinOutput>)
@ApolloExperimental
fun registerExtraCodeGenerator(codeGenerator: CodeGenerator)
}

/**
* A code generator that may write code in [ApolloCompilerPluginEnvironment.outputDirectory]
*
* This is not a kotlin function type because this might be used in environments where those types are
* relocated and might fail to load at runtime. For an example, in a Gradle plugin.
*/
fun interface CodeGenerator {
/**
* Transforms the given input into an output of the same type
*/
fun generate(schema: GQLDocument, outputDirectory: File)
@ApolloExperimental
fun registerSchemaCodeGenerator(schemaCodeGenerator: SchemaCodeGenerator)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.apollographql.apollo.compiler

import com.apollographql.apollo.annotations.ApolloExperimental
import com.apollographql.apollo.annotations.ApolloDeprecatedSince

/**
* [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
@Deprecated("Use ApolloCompilerPlugin directly.")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_2_1)
fun interface ApolloCompilerPluginProvider {
/**
* Creates the [ApolloCompilerPlugin]
Expand Down
Loading
Loading