Skip to content
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

Add ApolloCompilerPlugin.schemaDocumentListener() #6165

Merged
merged 2 commits into from
Sep 30, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.apollographql.apollo.ast

import com.apollographql.apollo.annotations.ApolloExperimental

/**
* A known foreign schema
*
* @param name the name of the foreign schema as in https://specs.apollo.dev/link/v1.0/#@link.url
* @param version the version of the foreign schema as in https://specs.apollo.dev/link/v1.0/#@link.url
* @param definitions the definitions in the foreign schema
* @param directivesToStrip the name of directives that must be stripped before being sent to the server
* without the leading '@'.
* For an example: `"catch"`
*/
@ApolloExperimental
class ForeignSchema(
val name: String,
val version: String,
val definitions: List<GQLDefinition>,
val directivesToStrip: List<String> = definitions.filterIsInstance<GQLDirective>().map { it.name },
)
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ fun GQLDocument.validateAsSchemaAndAddApolloDefinition(): GQLResult<Schema> {
definitions,
SchemaValidationOptions(
true,
supportedForeignSchemas()
builtinForeignSchemas()
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.apollographql.apollo.annotations.ApolloDeprecatedSince
import com.apollographql.apollo.annotations.ApolloExperimental
import com.apollographql.apollo.annotations.ApolloInternal
import com.apollographql.apollo.ast.internal.ExtensionsMerger
import com.apollographql.apollo.ast.internal.ForeignSchema
import com.apollographql.apollo.ast.internal.builtinsDefinitionsStr
import com.apollographql.apollo.ast.internal.ensureSchemaDefinition
import com.apollographql.apollo.ast.internal.kotlinLabsDefinitions_0_3
Expand Down Expand Up @@ -103,7 +102,7 @@ fun kotlinLabsDefinitions(version: String): List<GQLDefinition> {
* This is exported in case users want to validate documents meant for Apollo Kotlin.
*/
@ApolloExperimental
fun supportedForeignSchemas(): List<ForeignSchema> {
fun builtinForeignSchemas(): List<ForeignSchema> {
return listOf(
ForeignSchema("kotlin_labs", "v0.2", kotlinLabsDefinitions("v0.2"), listOf("optional", "nonnull")),
ForeignSchema("kotlin_labs", "v0.3", kotlinLabsDefinitions("v0.3"), listOf("optional", "nonnull")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.apollographql.apollo.annotations.ApolloExperimental
import com.apollographql.apollo.annotations.ApolloInternal
import com.apollographql.apollo.ast.ConflictResolution
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.GQLDirectiveDefinition
Expand Down Expand Up @@ -285,24 +286,6 @@ private class Import(
val newNames: Map<String, String>,
)

/**
* A known foreign schema
*
* @param name the name of the foreign schema as in https://specs.apollo.dev/link/v1.0/#@link.url
* @param version the version of the foreign schema as in https://specs.apollo.dev/link/v1.0/#@link.url
* @param definitions the definitions in the foreign schema
* @param directivesToStrip the name of directives that must be stripped before being sent to the server
* without the leading '@'.
* For an example: `"catch"`
*/
@ApolloExperimental
class ForeignSchema(
val name: String,
val version: String,
val definitions: List<GQLDefinition>,
val directivesToStrip: List<String> = definitions.filterIsInstance<GQLDirective>().map { it.name },
)

private class UrlParseResult(val name: String, val version: String)

private fun String.parseLink(): UrlParseResult? {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
package com.apollographql.apollo.graphql.ast.test

import com.apollographql.apollo.ast.GQLDirectiveDefinition
import com.apollographql.apollo.ast.GQLDirectiveLocation
import com.apollographql.apollo.ast.GQLInputValueDefinition
import com.apollographql.apollo.ast.internal.ForeignSchema
import com.apollographql.apollo.ast.ForeignSchema
import com.apollographql.apollo.ast.internal.SchemaValidationOptions
import com.apollographql.apollo.ast.parseAsGQLDocument
import com.apollographql.apollo.ast.toGQLDocument
import com.apollographql.apollo.ast.toSchema
import com.apollographql.apollo.ast.validateAsSchema
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class SchemaTest {
@Test
Expand Down
2 changes: 2 additions & 0 deletions libraries/apollo-compiler/api/apollo-compiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ public final class com/apollographql/apollo/compiler/AdapterInitializer$Companio
public final class com/apollographql/apollo/compiler/ApolloCompiler {
public static final field INSTANCE Lcom/apollographql/apollo/compiler/ApolloCompiler;
public final fun buildCodegenSchema (Ljava/util/List;Lcom/apollographql/apollo/compiler/ApolloCompiler$Logger;Lcom/apollographql/apollo/compiler/CodegenSchemaOptions;)Lcom/apollographql/apollo/compiler/CodegenSchema;
public final fun buildCodegenSchema (Ljava/util/List;Lcom/apollographql/apollo/compiler/ApolloCompiler$Logger;Lcom/apollographql/apollo/compiler/CodegenSchemaOptions;Ljava/util/List;)Lcom/apollographql/apollo/compiler/CodegenSchema;
public final fun buildIrOperations (Lcom/apollographql/apollo/compiler/CodegenSchema;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/apollographql/apollo/compiler/IrOptions;Lcom/apollographql/apollo/compiler/DocumentTransform;Lcom/apollographql/apollo/compiler/ApolloCompiler$Logger;)Lcom/apollographql/apollo/compiler/ir/IrOperations;
public final fun buildSchemaAndOperationsSources (Lcom/apollographql/apollo/compiler/CodegenSchema;Ljava/util/List;Lcom/apollographql/apollo/compiler/IrOptions;Lcom/apollographql/apollo/compiler/CodegenOptions;Lcom/apollographql/apollo/compiler/LayoutFactory;Lcom/apollographql/apollo/compiler/OperationOutputGenerator;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/DocumentTransform;Lcom/apollographql/apollo/compiler/ApolloCompiler$Logger;Ljava/io/File;)Lcom/apollographql/apollo/compiler/codegen/SourceOutput;
public final fun buildSchemaAndOperationsSources (Ljava/util/List;Ljava/util/List;Lcom/apollographql/apollo/compiler/CodegenSchemaOptions;Lcom/apollographql/apollo/compiler/IrOptions;Lcom/apollographql/apollo/compiler/CodegenOptions;Lcom/apollographql/apollo/compiler/LayoutFactory;Lcom/apollographql/apollo/compiler/OperationOutputGenerator;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/DocumentTransform;Lcom/apollographql/apollo/compiler/ApolloCompiler$Logger;Ljava/io/File;)Lcom/apollographql/apollo/compiler/codegen/SourceOutput;
public final fun buildSchemaAndOperationsSourcesFromIr (Lcom/apollographql/apollo/compiler/CodegenSchema;Lcom/apollographql/apollo/compiler/ir/IrOperations;Lcom/apollographql/apollo/compiler/UsedCoordinates;Ljava/util/List;Lcom/apollographql/apollo/compiler/CodegenOptions;Lcom/apollographql/apollo/compiler/codegen/SchemaAndOperationsLayout;Lcom/apollographql/apollo/compiler/OperationOutputGenerator;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/Transform;Ljava/io/File;)Lcom/apollographql/apollo/compiler/codegen/SourceOutput;
public final fun buildSchemaSources (Lcom/apollographql/apollo/compiler/CodegenSchema;Lcom/apollographql/apollo/compiler/UsedCoordinates;Lcom/apollographql/apollo/compiler/CodegenOptions;Lcom/apollographql/apollo/compiler/codegen/SchemaLayout;Lcom/apollographql/apollo/compiler/Transform;Lcom/apollographql/apollo/compiler/Transform;)Lcom/apollographql/apollo/compiler/codegen/SourceOutput;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package com.apollographql.apollo.compiler
import com.apollographql.apollo.ast.DeprecatedUsage
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.GQLDirectiveDefinition
import com.apollographql.apollo.ast.GQLDocument
import com.apollographql.apollo.ast.GQLFragmentDefinition
import com.apollographql.apollo.ast.GQLOperationDefinition
Expand All @@ -13,21 +13,20 @@ import com.apollographql.apollo.ast.GQLSchemaDefinition
import com.apollographql.apollo.ast.GQLTypeDefinition
import com.apollographql.apollo.ast.IncompatibleDefinition
import com.apollographql.apollo.ast.Issue
import com.apollographql.apollo.ast.KOTLIN_LABS_VERSION
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
import com.apollographql.apollo.ast.checkEmpty
import com.apollographql.apollo.ast.kotlinLabsDefinitions
import com.apollographql.apollo.ast.internal.SchemaValidationOptions
import com.apollographql.apollo.ast.parseAsGQLDocument
import com.apollographql.apollo.ast.pretty
import com.apollographql.apollo.ast.builtinForeignSchemas
import com.apollographql.apollo.ast.toGQLDocument
import com.apollographql.apollo.ast.validateAsExecutable
import com.apollographql.apollo.ast.validateAsSchemaAndAddApolloDefinition
import com.apollographql.apollo.compiler.codegen.LayoutImpl
import com.apollographql.apollo.ast.validateAsSchema
import com.apollographql.apollo.compiler.codegen.SchemaAndOperationsLayout
import com.apollographql.apollo.compiler.codegen.SchemaLayout
import com.apollographql.apollo.compiler.codegen.SourceOutput
Expand All @@ -49,7 +48,6 @@ import com.apollographql.apollo.compiler.ir.IrOperations
import com.apollographql.apollo.compiler.ir.IrOperationsBuilder
import com.apollographql.apollo.compiler.ir.IrSchema
import com.apollographql.apollo.compiler.ir.IrSchemaBuilder
import com.apollographql.apollo.compiler.ir.IrTargetObject
import com.apollographql.apollo.compiler.operationoutput.OperationDescriptor
import com.apollographql.apollo.compiler.pqm.toPersistedQueryManifest
import java.io.File
Expand All @@ -63,6 +61,20 @@ object ApolloCompiler {
schemaFiles: List<InputFile>,
logger: Logger?,
codegenSchemaOptions: CodegenSchemaOptions,
): CodegenSchema {
return buildCodegenSchema(
schemaFiles,
logger,
codegenSchemaOptions,
emptyList()
)
}

fun buildCodegenSchema(
schemaFiles: List<InputFile>,
logger: Logger?,
codegenSchemaOptions: CodegenSchemaOptions,
foreignSchemas: List<ForeignSchema>,
): CodegenSchema {
val schemaDocuments = schemaFiles.map {
it.normalizedPath to it.file.toGQLDocument(allowJson = true)
Expand Down Expand Up @@ -106,10 +118,15 @@ object ApolloCompiler {
sourceLocation = null
)

/**
* TODO: use `validateAsSchema` to not automatically add the apollo definitions
*/
val result = schemaDocument.validateAsSchemaAndAddApolloDefinition()
val result = schemaDocument.validateAsSchema(
validationOptions = SchemaValidationOptions(
/**
* TODO: switch to false
*/
addKotlinLabsDefinitions = true,
builtinForeignSchemas() + foreignSchemas
)
)

val issueGroup = result.issues.group(warnOnDeprecatedUsages = true, fieldsOnDisjointTypesMustMerge = true)

Expand Down Expand Up @@ -485,9 +502,44 @@ object ApolloCompiler {
val codegenSchema = buildCodegenSchema(
schemaFiles = schemaFiles,
logger = logger,
codegenSchemaOptions = codegenSchemaOptions
codegenSchemaOptions = codegenSchemaOptions,
foreignSchemas = emptyList()
)

return buildSchemaAndOperationsSources(
codegenSchema,
executableFiles,
irOptions,
codegenOptions,
layoutFactory,
operationOutputGenerator,
irOperationsTransform,
javaOutputTransform,
kotlinOutputTransform,
documentTransform,
logger,
operationManifestFile
)
}


/**
* Compiles a set of files without serializing the intermediate results
*/
fun buildSchemaAndOperationsSources(
codegenSchema: CodegenSchema,
executableFiles: List<InputFile>,
irOptions: IrOptions,
codegenOptions: CodegenOptions,
layoutFactory: LayoutFactory?,
@Suppress("DEPRECATION") operationOutputGenerator: OperationOutputGenerator?,
irOperationsTransform: Transform<IrOperations>?,
javaOutputTransform: Transform<JavaOutput>?,
kotlinOutputTransform: Transform<KotlinOutput>?,
documentTransform: DocumentTransform?,
logger: Logger?,
operationManifestFile: File?,
): SourceOutput {
val irOperations = buildIrOperations(
codegenSchema = codegenSchema,
executableFiles = executableFiles,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.apollographql.apollo.compiler

import com.apollographql.apollo.annotations.ApolloExperimental
import com.apollographql.apollo.ast.ForeignSchema
import com.apollographql.apollo.ast.GQLDocument
import com.apollographql.apollo.ast.GQLFragmentDefinition
import com.apollographql.apollo.ast.GQLOperationDefinition
import com.apollographql.apollo.ast.Schema
Expand All @@ -10,13 +12,15 @@ import com.apollographql.apollo.compiler.codegen.kotlin.KotlinOutput
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

/**
* [ApolloCompilerPlugin] allows to customize the behaviour of the Apollo Compiler.
*
* [ApolloCompilerPlugin] is run in an isolated classloader. You may throw from [ApolloCompilerPlugin] methods to fail the build
* but custom exception classes are not accessible from the calling environment like Gradle for an example.
* Prefer throwing regular Java exception classes.
* [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.
*/
interface ApolloCompilerPlugin {
/**
Expand Down Expand Up @@ -65,8 +69,37 @@ interface ApolloCompilerPlugin {
fun irOperationsTransform(): Transform<IrOperations>? {
return null
}

/**
* @return A list of [ForeignSchema] supported by this plugin
*/
@ApolloExperimental
fun foreignSchemas(): List<ForeignSchema> {
return emptyList()
}

/**
* @return A [SchemaDocumentListener] called whenever the schema changed
*/
@ApolloExperimental
fun schemaDocumentListener(): SchemaDocumentListener? {
return null
}
}

@ApolloExperimental
interface SchemaDocumentListener {
/**
* Called when the schema changed and codegen needs to be updated
*
* @param schema the validated schema document.
* @param outputDirectory the compiler output directory. This directory is shared with the compiler, make sure to use a specific
* package name to avoid clobbering other files.
*/
fun onSchemaDocument(schema: GQLDocument, outputDirectory: File)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I didn't catch it before, but would it make sense to also pass the packageName configured in the service here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it out on purpose. The package name can be turing complete (because of packageNameFromFilePath, etc...). How big of an issue is it to not have it? If really needed, one can always pass it as a ACP (Apollo Compiler Plugin) argument.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. Not a big issue, but I think this means the package name will have to be repeated in most cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question - why not passing the Schema rather than the GQLDocument? To correctly look at directives I should use Schema.originalDirectiveName() right?

Copy link
Contributor Author

@martinbonnin martinbonnin Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this means the package name will have to be repeated in most cases.

What about:

apollo {
  service("service") {
    packageName.set("com.example")
    plugin(project(":cache-plugin")) {
      argument("packageName", packageName.get())
    }
  }
}

And if evolution is a concern, hide it behind a java function:

apollo {
  service("service") {
    packageName.set("com.example")
    configureCachePlugin(this, project(":cache-plugin"))
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not passing the Schema rather than the GQLDocument?

Not sure. GQLDocument is overall more stable than Schema but yea in your case, looks like you need schema. I'll amend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6166 for passing a Schema instead of a GQLDocument

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

packageName.get() is totally acceptable, a dedicated fun as well 👍.

}


/**
* A [DocumentTransform] transforms operations and fragments at build time. [DocumentTransform] can add or remove fields automatically for an example.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,8 @@ private fun ApolloCompiler.buildSchemaAndOperationsSourcesAndReturnIrOperations(
val codegenSchema = buildCodegenSchema(
schemaFiles = schemaFiles,
logger = logger,
codegenSchemaOptions = codegenSchemaOptions
codegenSchemaOptions = codegenSchemaOptions,
foreignSchemas = emptyList()
)

val irOperations = buildIrOperations(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class MetadataTest {
schemaFiles = setOf(File("src/test/metadata/schema.graphqls")).toInputFiles(),
logger = null,
codegenSchemaOptions = codegenSchemaOptionsFile.toCodegenSchemaOptions(),
foreignSchemas = emptyList(),
).writeTo(codegenSchemaFile)

ApolloCompiler.buildIrOperations(
Expand Down
Loading
Loading