Skip to content

Commit

Permalink
Fix configuration cache and schema file auto detection
Browse files Browse the repository at this point in the history
Closes #5455
Closes #5221
  • Loading branch information
martinbonnin committed Jan 29, 2024
1 parent 71e1b51 commit 62c6b1c
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import java.io.File
Expand Down Expand Up @@ -50,8 +49,13 @@ abstract class ApolloDownloadSchemaTask : DefaultTask() {
@get:Option(option = "schema", description = "path where the schema will be downloaded, relative to the root project directory")
abstract val schema: Property<String>

@get:OutputFile
@get:Optional
/**
* This is not declared as an output as it triggers this Gradle error else:
* "Reason: Task ':root:generateServiceApolloCodegenSchema' uses this output of task ':root:downloadServiceApolloSchemaFromIntrospection' without declaring an explicit or implicit dependency."
*
* Since it's unlikely that users want to download the schema every time, just set it as an internal property.
*/
@get:Internal
abstract val outputFile: RegularFileProperty

@get:Internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ abstract class ApolloGenerateCodegenSchemaTask : DefaultTask() {
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val schemaFiles: ConfigurableFileCollection

@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val fallbackSchemaFiles: ConfigurableFileCollection

@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val upstreamSchemaFiles: ConfigurableFileCollection
Expand All @@ -47,7 +51,7 @@ abstract class ApolloGenerateCodegenSchemaTask : DefaultTask() {
}

ApolloCompiler.buildCodegenSchema(
schemaFiles = schemaFiles.files,
schemaFiles = schemaFiles.files.takeIf { it.isNotEmpty() } ?: fallbackSchemaFiles.files,
logger = logger(),
codegenSchemaOptions = codegenSchemaOptionsFile.get().asFile.toCodegenSchemaOptions(),
).writeTo(codegenSchemaFile.get().asFile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ abstract class ApolloGenerateSourcesTask : ApolloGenerateSourcesBaseTask() {
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val schemaFiles: ConfigurableFileCollection

@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val fallbackSchemaFiles: ConfigurableFileCollection

@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val codegenSchemaOptionsFile: RegularFileProperty
Expand All @@ -31,7 +35,7 @@ abstract class ApolloGenerateSourcesTask : ApolloGenerateSourcesBaseTask() {
@TaskAction
fun taskAction() {
ApolloCompiler.buildSchemaAndOperationsSources(
schemaFiles = schemaFiles.files,
schemaFiles = schemaFiles.files.takeIf { it.isNotEmpty() } ?: fallbackSchemaFiles.files,
executableFiles = graphqlFiles.files,
codegenSchemaOptions = codegenSchemaOptionsFile.get().asFile.toCodegenSchemaOptions(),
codegenOptions = codegenOptionsFile.get().asFile.toCodegenOptions(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import org.gradle.api.attributes.Usage
import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.api.component.SoftwareComponentFactory
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.provider.Property
import org.gradle.api.tasks.TaskProvider
Expand Down Expand Up @@ -57,7 +56,7 @@ abstract class DefaultApolloExtension(
internal fun getServiceInfos(project: Project): List<ApolloGradleToolingModel.ServiceInfo> = services.map { service ->
DefaultServiceInfo(
name = service.name,
schemaFiles = service.lazySchemaFiles(project),
schemaFiles = service.schemaFilesSnapshot(project),
graphqlSrcDirs = service.graphqlSourceDirectorySet.srcDirs,
upstreamProjects = service.upstreamDependencies.filterIsInstance<ProjectDependency>().map { it.name }.toSet(),
endpointUrl = service.introspection?.endpointUrl?.orNull,
Expand Down Expand Up @@ -362,7 +361,7 @@ abstract class DefaultApolloExtension(

if (service.graphqlSourceDirectorySet.isReallyEmpty) {
val sourceFolder = service.sourceFolder.getOrElse("")
val dir = File(project.projectDir, "src/${mainSourceSet(project)}/graphql/$sourceFolder")
val dir = File(project.projectDir, "src/${project.mainSourceSet()}/graphql/$sourceFolder")

service.graphqlSourceDirectorySet.srcDir(dir)
}
Expand Down Expand Up @@ -737,9 +736,8 @@ abstract class DefaultApolloExtension(
task.group = TASK_GROUP
task.description = "Generate Apollo schema for service '${service.name}'"

// This has to be lazy in case the schema is not written yet during configuration
// See the `graphql files can be generated by another task` test
task.schemaFiles.from(project.provider { service.lazySchemaFiles(project) })
task.schemaFiles.from(service.schemaFiles(project))
task.fallbackSchemaFiles.from(service.fallbackSchemaFiles(project))
task.upstreamSchemaFiles.from(schemaConsumerConfiguration)
task.codegenSchemaOptionsFile.set(optionsTaskProvider.flatMap { it.codegenSchemaOptionsFile })
task.codegenSchemaFile.set(BuildDirLayout.codegenSchema(project, service))
Expand Down Expand Up @@ -828,37 +826,13 @@ abstract class DefaultApolloExtension(

configureBaseCodegenTask(project, task, optionsTaskProvider, service)

// This has to be lazy in case the schema is not written yet during configuration
// See the `graphql files can be generated by another task` test
task.schemaFiles.from(project.provider { service.lazySchemaFiles(project) })
task.schemaFiles.from(service.schemaFiles(project))
task.fallbackSchemaFiles.from(service.fallbackSchemaFiles(project))
task.codegenSchemaOptionsFile.set(optionsTaskProvider.map { it.codegenSchemaOptionsFile.get() })
task.irOptionsFile.set(optionsTaskProvider.map { it.irOptionsFile.get() })
}
}

/**
* XXX: this returns an absolute path, which might be an issue for the build cache.
* I don't think this is much of an issue because tasks like ApolloDownloadSchemaTask don't have any
* outputs and are therefore never up-to-date so the build cache will not help much.
*
* If that ever becomes an issue, making the path relative to the project root might be a good idea.
*/
private fun lazySchemaFileForDownload(service: DefaultService, schemaFile: RegularFileProperty): File {
if (schemaFile.isPresent) {
return schemaFile.get().asFile
}

val candidates = service.lazySchemaFiles(project)
check(candidates.isNotEmpty()) {
"No schema files found. Specify introspection.schemaFile or registry.schemaFile"
}
check(candidates.size == 1) {
"Multiple schema files found:\n${candidates.joinToString("\n")}\n\nSpecify introspection.schemaFile or registry.schemaFile"
}

return candidates.single()
}

private fun registerDownloadSchemaTasks(service: DefaultService) {
val introspection = service.introspection
var taskProvider: TaskProvider<ApolloDownloadSchemaTask>? = null
Expand All @@ -868,7 +842,7 @@ abstract class DefaultApolloExtension(
taskProvider = project.tasks.register(ModelNames.downloadApolloSchemaIntrospection(service), ApolloDownloadSchemaTask::class.java) { task ->

task.group = TASK_GROUP
task.outputFile.set(lazySchemaFileForDownload(service, introspection.schemaFile))
task.outputFile.set(service.guessSchemaFile(project, introspection.schemaFile))
task.endpoint.set(introspection.endpointUrl)
task.header = introspection.headers.get().map { "${it.key}: ${it.value}" }
}
Expand All @@ -879,7 +853,7 @@ abstract class DefaultApolloExtension(
taskProvider = project.tasks.register(ModelNames.downloadApolloSchemaRegistry(service), ApolloDownloadSchemaTask::class.java) { task ->

task.group = TASK_GROUP
task.outputFile.set(lazySchemaFileForDownload(service, registry.schemaFile))
task.outputFile.set(service.guessSchemaFile(project, registry.schemaFile))
task.graph.set(registry.graph)
task.key.set(registry.key)
task.graphVariant.set(registry.graphVariant)
Expand Down Expand Up @@ -981,38 +955,6 @@ abstract class DefaultApolloExtension(
private val SourceDirectorySet.isReallyEmpty
get() = sourceDirectories.isEmpty

private fun mainSourceSet(project: Project): String {
return when (project.extensions.findByName("kotlin")) {
is KotlinMultiplatformExtension -> "commonMain"
else -> "main"
}
}

/**
* May return an empty set
*/
fun DefaultService.lazySchemaFiles(project: Project): Set<File> {
val files = if (schemaFile.isPresent) {
check(schemaFiles.isEmpty) {
"Specifying both schemaFile and schemaFiles is an error"
}
project.files(schemaFile)
} else {
schemaFiles
}

if (!files.isEmpty) {
return files.files
}

return graphqlSourceDirectorySet.srcDirs.flatMap { srcDir ->
srcDir.walkTopDown().filter {
it.extension in listOf("json", "sdl", "graphqls")
&& !it.name.startsWith("used") // Avoid detecting the used coordinates as a schema
}.toList()
}.toSet()
}

internal fun Project.hasJavaPlugin() = project.extensions.findByName("java") != null
internal fun Project.hasKotlinPlugin() = project.extensions.findByName("kotlin") != null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import com.apollographql.apollo3.gradle.api.Service
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import java.io.File
import javax.inject.Inject

Expand Down Expand Up @@ -199,3 +202,70 @@ abstract class DefaultService @Inject constructor(val project: Project, override
internal fun isSchemaModule(): Boolean = upstreamDependencies.isEmpty()
}

internal fun DefaultService.fallbackFiles(project: Project, block: (ConfigurableFileTree) -> Unit): FileCollection {
val fileCollection = project.files()

graphqlSourceDirectorySet.srcDirs.forEach { directory ->
fileCollection.from(project.fileTree(directory, block))
}

return fileCollection
}

internal fun DefaultService.schemaFiles(project: Project): FileCollection {
val fileCollection = project.files()

if (schemaFile.isPresent) {
fileCollection.from(schemaFile)
} else {
fileCollection.from(schemaFiles)
}

return fileCollection
}

/**
* ConfigurableFileCollections have no way to check for absent vs empty
* [schemaFiles] can be empty at configuration time because the task responsible
* to create the file did not run yet but still be set (because it will ultimately
* create the file)
*
* The only workaround I found is to pass both to the task and defer the decision
* which one to choose to execution time.
*
* See https://github.com/gradle/gradle/issues/21752
*/
internal fun DefaultService.fallbackSchemaFiles(project: Project): FileCollection {
return fallbackFiles(project) { configurableFileTree ->
configurableFileTree.include(listOf("**/*.graphqls", "**/*.json", "**/*.sdl"))
}
}

/**
* Returns a snapshot of the schema files. Some of the schema files might be missing if generated
* from another task
*/
internal fun DefaultService.schemaFilesSnapshot(project: Project): Set<File> {
return schemaFiles(project).files.takeIf { it.isNotEmpty() } ?: fallbackSchemaFiles(project).files
}

/**
* Tries to guess where the schema file is.
* This can fail when:
* - there are several schema files.
* - the schema file is not written yet (because it needs to be written by another task)
*/
internal fun DefaultService.guessSchemaFile(project: Project, schemaFile: RegularFileProperty): File {
if (schemaFile.isPresent) {
return schemaFile.get().asFile
}
val candidates = schemaFilesSnapshot(project)
check(candidates.isNotEmpty()) {
"No schema files found. Specify introspection.schemaFile or registry.schemaFile"
}
check(candidates.size == 1) {
"Multiple schema files found:\n${candidates.joinToString("\n")}\n\nSpecify introspection.schemaFile or registry.schemaFile"
}

return candidates.single()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.apollographql.apollo3.gradle.internal

import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

internal fun Project.mainSourceSet(): String {
return when (project.extensions.findByName("kotlin")) {
is KotlinMultiplatformExtension -> "commonMain"
else -> "main"
}
}

0 comments on commit 62c6b1c

Please sign in to comment.