Skip to content

Commit

Permalink
Add DocumentTransform API (#5809)
Browse files Browse the repository at this point in the history
* Add DocumentTransform API

* Fix ConditionalFragments test

* Add link to the documentation

* add missing parameter
  • Loading branch information
martinbonnin authored Apr 16, 2024
1 parent 6ee32df commit c016f84
Show file tree
Hide file tree
Showing 16 changed files with 206 additions and 27 deletions.
1 change: 1 addition & 0 deletions gradle/libraries.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ apollo-rx2-support-java = { group = "com.apollographql.apollo3", name = "apollo-
apollo-plugin = { group = "com.apollographql.apollo3", name = "apollo-gradle-plugin", version.ref = "apollo" }
apollo-runtime = { group = "com.apollographql.apollo3", name = "apollo-runtime", version.ref = "apollo" }
apollo-compiler = { group = "com.apollographql.apollo3", name = "apollo-compiler", version.ref = "apollo" }
apollo-ast = { group = "com.apollographql.apollo3", name = "apollo-ast", version.ref = "apollo" }
apollo-execution = { group = "com.apollographql.apollo3", name = "apollo-execution-incubating", version.ref = "apollo" }
# Used by the apollo-tooling project which uses a published version of Apollo
apollo-runtime-published = { group = "com.apollographql.apollo3", name = "apollo-runtime", version.ref = "apollo-published" }
Expand Down
4 changes: 2 additions & 2 deletions libraries/apollo-compiler/api/apollo-compiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public final class com/apollographql/apollo3/compiler/ApolloCompiler {
public static final field INSTANCE Lcom/apollographql/apollo3/compiler/ApolloCompiler;
public final fun buildCodegenSchema (Ljava/util/List;Lcom/apollographql/apollo3/compiler/ApolloCompiler$Logger;Lcom/apollographql/apollo3/compiler/CodegenSchemaOptions;)Lcom/apollographql/apollo3/compiler/CodegenSchema;
public final fun buildExecutableSchemaSources (Lcom/apollographql/apollo3/compiler/CodegenSchema;Lcom/apollographql/apollo3/compiler/CodegenMetadata;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/apollo3/compiler/codegen/SourceOutput;
public final fun buildIrOperations (Lcom/apollographql/apollo3/compiler/CodegenSchema;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/apollographql/apollo3/compiler/IrOptions;Lcom/apollographql/apollo3/compiler/ApolloCompiler$Logger;)Lcom/apollographql/apollo3/compiler/ir/IrOperations;
public final fun buildSchemaAndOperationsSources (Ljava/util/List;Ljava/util/List;Lcom/apollographql/apollo3/compiler/CodegenSchemaOptions;Lcom/apollographql/apollo3/compiler/IrOptions;Lcom/apollographql/apollo3/compiler/CodegenOptions;Lcom/apollographql/apollo3/compiler/LayoutFactory;Lcom/apollographql/apollo3/compiler/OperationOutputGenerator;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/ApolloCompiler$Logger;Ljava/io/File;)Lcom/apollographql/apollo3/compiler/codegen/SourceOutput;
public final fun buildIrOperations (Lcom/apollographql/apollo3/compiler/CodegenSchema;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/apollographql/apollo3/compiler/IrOptions;Lcom/apollographql/apollo3/compiler/DocumentTransform;Lcom/apollographql/apollo3/compiler/ApolloCompiler$Logger;)Lcom/apollographql/apollo3/compiler/ir/IrOperations;
public final fun buildSchemaAndOperationsSources (Ljava/util/List;Ljava/util/List;Lcom/apollographql/apollo3/compiler/CodegenSchemaOptions;Lcom/apollographql/apollo3/compiler/IrOptions;Lcom/apollographql/apollo3/compiler/CodegenOptions;Lcom/apollographql/apollo3/compiler/LayoutFactory;Lcom/apollographql/apollo3/compiler/OperationOutputGenerator;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/DocumentTransform;Lcom/apollographql/apollo3/compiler/ApolloCompiler$Logger;Ljava/io/File;)Lcom/apollographql/apollo3/compiler/codegen/SourceOutput;
public final fun buildSchemaAndOperationsSourcesFromIr (Lcom/apollographql/apollo3/compiler/CodegenSchema;Lcom/apollographql/apollo3/compiler/ir/IrOperations;Lcom/apollographql/apollo3/compiler/UsedCoordinates;Ljava/util/List;Lcom/apollographql/apollo3/compiler/CodegenOptions;Lcom/apollographql/apollo3/compiler/codegen/SchemaAndOperationsLayout;Lcom/apollographql/apollo3/compiler/OperationOutputGenerator;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/Transform;Ljava/io/File;)Lcom/apollographql/apollo3/compiler/codegen/SourceOutput;
public final fun buildSchemaSources (Lcom/apollographql/apollo3/compiler/CodegenSchema;Lcom/apollographql/apollo3/compiler/UsedCoordinates;Lcom/apollographql/apollo3/compiler/CodegenOptions;Lcom/apollographql/apollo3/compiler/codegen/SchemaLayout;Lcom/apollographql/apollo3/compiler/Transform;Lcom/apollographql/apollo3/compiler/Transform;)Lcom/apollographql/apollo3/compiler/codegen/SourceOutput;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ object ApolloCompiler {
upstreamCodegenModels: List<String>,
upstreamFragmentDefinitions: List<GQLFragmentDefinition>,
options: IrOptions,
documentTransform: DocumentTransform?,
logger: Logger?,
): IrOperations {
val schema = codegenSchema.schema
Expand Down Expand Up @@ -240,11 +241,15 @@ object ApolloCompiler {
*/
val fragmentDefinitions = (definitions.filterIsInstance<GQLFragmentDefinition>() + upstreamFragmentDefinitions).associateBy { it.name }
val fragments = definitions.filterIsInstance<GQLFragmentDefinition>().map {
addRequiredFields(it, addTypename, schema, fragmentDefinitions)
addRequiredFields(it, addTypename, schema, fragmentDefinitions).let {
documentTransform?.transform(schema, it) ?: it
}
}

val operations = definitions.filterIsInstance<GQLOperationDefinition>().map {
addRequiredFields(it, addTypename, schema, fragmentDefinitions)
addRequiredFields(it, addTypename, schema, fragmentDefinitions).let {
documentTransform?.transform(schema, it) ?: it
}
}

// Remember the fragments with the possibly updated fragments
Expand Down Expand Up @@ -465,6 +470,7 @@ object ApolloCompiler {
irOperationsTransform: Transform<IrOperations>?,
javaOutputTransform: Transform<JavaOutput>?,
kotlinOutputTransform: Transform<KotlinOutput>?,
documentTransform: DocumentTransform?,
logger: Logger?,
operationManifestFile: File?,
): SourceOutput {
Expand All @@ -479,6 +485,7 @@ object ApolloCompiler {
executableFiles = executableFiles,
upstreamCodegenModels = emptyList(),
upstreamFragmentDefinitions = emptyList(),
documentTransform = documentTransform,
options = irOptions,
logger = logger
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.apollographql.apollo3.compiler

import com.apollographql.apollo3.annotations.ApolloExperimental
import com.apollographql.apollo3.ast.GQLFragmentDefinition
import com.apollographql.apollo3.ast.GQLOperationDefinition
import com.apollographql.apollo3.ast.Schema
import com.apollographql.apollo3.compiler.codegen.SchemaAndOperationsLayout
import com.apollographql.apollo3.compiler.codegen.java.JavaOutput
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinOutput
Expand Down Expand Up @@ -31,28 +34,55 @@ interface Plugin {
}

/**
* @return the [Transform] to be applied to [IrOperations] or null to use the default [Transform]
* @return the [Transform] to be applied to [JavaOutput] or null to use the default [Transform]
*/
@ApolloExperimental
fun irOperationsTransform(): Transform<IrOperations>? {
fun javaOutputTransform(): Transform<JavaOutput>? {
return null
}

/**
* @return the [Transform] to be applied to [JavaOutput] or null to use the default [Transform]
* @return the [Transform] to be applied to [KotlinOutput] or null to use the default [Transform]
*/
fun javaOutputTransform(): Transform<JavaOutput>? {
fun kotlinOutputTransform(): Transform<KotlinOutput>? {
return null
}

/**
* @return the [Transform] to be applied to [KotlinOutput] or null to use the default [Transform]
* @return a [DocumentTransform] to transform operations and/or fragments
*/
fun kotlinOutputTransform(): Transform<KotlinOutput>? {
@ApolloExperimental
fun documentTransform(): DocumentTransform? {
return null
}

/**
* @return the [Transform] to be applied to [IrOperations] or null to use the default [Transform]
*/
@ApolloExperimental
fun irOperationsTransform(): Transform<IrOperations>? {
return null
}
}

/**
* A [DocumentTransform] transforms operations and fragments at build time. [DocumentTransform] can add or remove fields automatically for an example.
*/
@ApolloExperimental
interface DocumentTransform {
/**
* Transforms the given operation.
*
* [transform] is called after any processing done by the Apollo compiler such as adding `__typename`.
*/
fun transform(schema: Schema, operation: GQLOperationDefinition): GQLOperationDefinition
/**
* Transforms the given fragment.
*
* [transform] is called after any processing done by the Apollo compiler such as adding `__typename`.
*/
fun transform(schema: Schema, fragment: GQLFragmentDefinition): GQLFragmentDefinition
}

/**
* Transforms a type
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ private fun ApolloCompiler.buildSchemaAndOperationsSourcesAndReturnIrOperations(
executableFiles = executableFiles,
upstreamCodegenModels = emptyList(),
upstreamFragmentDefinitions = emptyList(),
documentTransform = null,
options = irOptions,
logger = logger
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class MetadataTest {
executableFiles = setOf(rootGraphQLFile(directory)).toInputFiles(),
upstreamCodegenModels = emptyList(),
upstreamFragmentDefinitions = emptyList(),
documentTransform = null,
options = irOptionsFile.toIrOptions(),
logger = null
).writeTo(rootIrOperationsFile)
Expand All @@ -60,6 +61,7 @@ class MetadataTest {
executableFiles = setOf(leafGraphQLFile(directory)).toInputFiles(),
upstreamCodegenModels = rootIrOperationsFile.toIrOperations().codegenModels.let { listOf(it) },
upstreamFragmentDefinitions = rootIrOperationsFile.toIrOperations().fragmentDefinitions,
documentTransform = null,
options = irOptionsFile.toIrOptions(),
logger = null
).writeTo(leafIrOperationsFile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ConditionalFragmentsTest {
irOperationsTransform = null,
javaOutputTransform = null,
kotlinOutputTransform = null,
documentTransform = null,
)
}

Expand All @@ -59,6 +60,7 @@ class ConditionalFragmentsTest {
irOperationsTransform = null,
javaOutputTransform = null,
kotlinOutputTransform = null,
documentTransform = null,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import com.apollographql.apollo3.gradle.internal.ApolloGenerateSourcesFromIrTask
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
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import java.io.File
import javax.inject.Inject

abstract class ApolloGenerateIrOperationsTask: DefaultTask() {
@get:InputFiles
Expand All @@ -36,19 +42,51 @@ abstract class ApolloGenerateIrOperationsTask: DefaultTask() {
@get:OutputFile
abstract val irOperationsFile: RegularFileProperty

@get:Classpath
abstract val classpath: ConfigurableFileCollection

@Inject
abstract fun getWorkerExecutor(): WorkerExecutor

@TaskAction
fun taskAction() {
val upstreamIrOperations = upstreamIrFiles.files.map { it.toIrOperations() }

val normalizedExecutableFiles = graphqlFiles.toInputFiles()

ApolloCompiler.buildIrOperations(
executableFiles = normalizedExecutableFiles,
codegenSchema = codegenSchemaFiles.files.findCodegenSchemaFile().toCodegenSchema(),
upstreamCodegenModels = upstreamIrOperations.map { it.codegenModels },
upstreamFragmentDefinitions = upstreamIrOperations.flatMap { it.fragmentDefinitions },
options = irOptionsFile.get().asFile.toIrOptions(),
logger = logger(),
).writeTo(irOperationsFile.get().asFile)

val workQueue = getWorkerExecutor().classLoaderIsolation { workerSpec ->
workerSpec.classpath.from(classpath)
}

workQueue.submit(GenerateIrOperations::class.java) {
it.codegenSchemaFiles = codegenSchemaFiles.isolate()
it.graphqlFiles = graphqlFiles.isolate()
it.upstreamIrFiles = upstreamIrFiles.isolate()
it.irOptionsFile.set(irOptionsFile)
it.irOperationsFile.set(irOperationsFile)
}
}
}

private abstract class GenerateIrOperations : WorkAction<GenerateIrOperationsParameters> {
override fun execute() {
with(parameters) {
val upstreamIrOperations = upstreamIrFiles.toInputFiles().map { it.file.toIrOperations() }
val plugin = apolloCompilerPlugin()

ApolloCompiler.buildIrOperations(
executableFiles = graphqlFiles.toInputFiles(),
codegenSchema = codegenSchemaFiles.toInputFiles().map { it.file }.findCodegenSchemaFile().toCodegenSchema(),
upstreamCodegenModels = upstreamIrOperations.map { it.codegenModels },
upstreamFragmentDefinitions = upstreamIrOperations.flatMap { it.fragmentDefinitions },
documentTransform = plugin?.documentTransform(),
options = irOptionsFile.get().asFile.toIrOptions(),
logger = logger(),
).writeTo(irOperationsFile.get().asFile)
}
}
}
}
private interface GenerateIrOperationsParameters : WorkParameters {
var codegenSchemaFiles: List<Pair<String, File>>
var graphqlFiles: List<Pair<String, File>>
var upstreamIrFiles: List<Pair<String, File>>
val irOptionsFile: RegularFileProperty
val irOperationsFile: RegularFileProperty
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ fun ApolloGenerateSourcesBaseTask.layout(): LayoutFactory {
fun ApolloGenerateSourcesBaseTask.requiresBuildscriptClasspath(): Boolean {
if (packageNameGenerator != null || operationOutputGenerator != null) {
if (packageNameGenerator != null) {
logger.lifecycle("Apollo: packageNameGenerator is deprecated, use Apollo compiler plugins instead")
logger.lifecycle("Apollo: packageNameGenerator is deprecated, use Apollo compiler plugins instead. See https://go.apollo.dev/ak-compiler-plugins for more details.")
}
if (operationOutputGenerator != null) {
logger.lifecycle("Apollo: operationOutputGenerator is deprecated, use Apollo compiler plugins instead")
logger.lifecycle("Apollo: operationOutputGenerator is deprecated, use Apollo compiler plugins instead. See https://go.apollo.dev/ak-compiler-plugins for more details.")
}

return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ abstract class ApolloGenerateSourcesTask : ApolloGenerateSourcesBaseTask() {
irOperationsTransform = null,
javaOutputTransform = null,
kotlinOutputTransform = null,
documentTransform = null,
operationManifestFile = operationManifestFile.orNull?.asFile
).writeTo(outputDir.get().asFile, true, null)
} else {
Expand Down Expand Up @@ -104,6 +105,7 @@ private abstract class GenerateSources : WorkAction<GenerateSourcesParameters> {
irOperationsTransform = plugin?.irOperationsTransform(),
javaOutputTransform = plugin?.javaOutputTransform(),
kotlinOutputTransform = plugin?.kotlinOutputTransform(),
documentTransform = plugin?.documentTransform(),
operationManifestFile = operationManifestFile.orNull?.asFile
).writeTo(outputDir.get().asFile, true, null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,9 @@ abstract class DefaultApolloExtension(
schemaConsumerConfiguration = codegenSchemaConsumerConfiguration,
schemaTaskProvider = codegenSchemaTaskProvider,
irOptionsTaskProvider = optionsTaskProvider,
upstreamIrFiles = upstreamIrConsumerConfiguration)
upstreamIrFiles = upstreamIrConsumerConfiguration,
classpath = pluginConfiguration
)

val sourcesFromIrTaskProvider = registerSourcesFromIrTask(
project = project,
Expand Down Expand Up @@ -780,11 +782,13 @@ abstract class DefaultApolloExtension(
schemaTaskProvider: TaskProvider<ApolloGenerateCodegenSchemaTask>?,
irOptionsTaskProvider: TaskProvider<ApolloGenerateOptionsTask>,
upstreamIrFiles: Configuration,
classpath: FileCollection,
): TaskProvider<ApolloGenerateIrOperationsTask> {
return project.tasks.register(ModelNames.generateApolloIrOperations(service), ApolloGenerateIrOperationsTask::class.java) { task ->
task.group = TASK_GROUP
task.description = "Generate Apollo IR operations for service '${service.name}'"

task.classpath.from(classpath)
task.codegenSchemaFiles.from(schemaConsumerConfiguration)
if (schemaTaskProvider != null) {
task.codegenSchemaFiles.from(schemaTaskProvider.flatMap { it.codegenSchemaFile })
Expand Down
10 changes: 10 additions & 0 deletions tests/compiler-plugins/add-field/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id("org.jetbrains.kotlin.jvm")
}

apolloTest()

dependencies {
implementation(libs.apollo.compiler)
implementation(libs.apollo.ast)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package hooks

import com.apollographql.apollo3.ast.GQLField
import com.apollographql.apollo3.ast.GQLFragmentDefinition
import com.apollographql.apollo3.ast.GQLFragmentSpread
import com.apollographql.apollo3.ast.GQLInlineFragment
import com.apollographql.apollo3.ast.GQLOperationDefinition
import com.apollographql.apollo3.ast.GQLSelection
import com.apollographql.apollo3.ast.Schema
import com.apollographql.apollo3.ast.definitionFromScope
import com.apollographql.apollo3.ast.rawType
import com.apollographql.apollo3.ast.responseName
import com.apollographql.apollo3.ast.rootTypeDefinition
import com.apollographql.apollo3.compiler.DocumentTransform
import com.apollographql.apollo3.compiler.Plugin

class TestPlugin : Plugin {
override fun documentTransform(): DocumentTransform {
return object : DocumentTransform {
override fun transform(schema: Schema, operation: GQLOperationDefinition): GQLOperationDefinition {
return operation.copy(
selections = operation.selections.alwaysGreet(schema, operation.rootTypeDefinition(schema)!!.name)
)
}

override fun transform(schema: Schema, fragment: GQLFragmentDefinition): GQLFragmentDefinition {
return fragment.copy(
selections = fragment.selections.alwaysGreet(schema, fragment.typeCondition.name)
)
}
}
}

private fun List<GQLSelection>.alwaysGreet(schema: Schema, parentType: String): List<GQLSelection> {
val selections = this.map {
when (it) {
is GQLField -> it.copy(
selections = it.selections.alwaysGreet(schema, it.definitionFromScope(schema, parentType)!!.type.rawType().name)
)
is GQLFragmentSpread -> it
is GQLInlineFragment -> it.copy(
selections = it.selections.alwaysGreet(schema, it.typeCondition?.name ?: parentType)
)
}
}

return if (parentType == "Friend" && selections.none { it is GQLField && it.responseName() == "greet" }) {
selections + GQLField(null, null, "greet", emptyList(), emptyList(), emptyList())
} else {
selections
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hooks.TestPlugin
Loading

0 comments on commit c016f84

Please sign in to comment.