diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bc1ec18..c19dc6a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin-plugin = "2.1.21" intellij-platform-plugin = "2.7.0" -apollo = "4.3.1" +apollo = "5.0.0-alpha.2-SNAPSHOT" grammarkit-plugin = "2022.3.2.2" changelog-plugin = "2.2.1" sqlite-jdbc = "3.43.2.0" @@ -32,9 +32,10 @@ changelog = { id = "org.jetbrains.changelog", version.ref = "changelog-plugin" } apollo-annotations = { group = "com.apollographql.apollo", name = "apollo-annotations", version.ref = "apollo" } apollo-api = { group = "com.apollographql.apollo", name = "apollo-api", version.ref = "apollo" } apollo-runtime = { group = "com.apollographql.apollo", name = "apollo-runtime", version.ref = "apollo" } -apollo-gradle-plugin-external = { group = "com.apollographql.apollo", name = "apollo-gradle-plugin-external", version.ref = "apollo" } +apollo-gradle-plugin = { group = "com.apollographql.apollo", name = "apollo-gradle-plugin", version.ref = "apollo" } apollo-ast = { group = "com.apollographql.apollo", name = "apollo-ast", version.ref = "apollo" } apollo-tooling = { group = "com.apollographql.apollo", name = "apollo-tooling", version.ref = "apollo" } +apollo-compiler = { group = "com.apollographql.apollo", name = "apollo-compiler", version.ref = "apollo" } apollo-normalized-cache-sqlite-classic = { group = "com.apollographql.apollo", name = "apollo-normalized-cache-sqlite", version.ref = "apollo" } apollo-normalized-cache-sqlite-new = { group = "com.apollographql.cache", name = "normalized-cache-sqlite", version.ref = "apollo-normalizedcache" } sqlite-jdbc = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqlite-jdbc" } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index de23f5a..15b9b35 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -20,10 +20,10 @@ plugins { } repositories { -// mavenLocal() // maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // maven("https://storage.googleapis.com/apollo-previews/m2/") mavenCentral() + mavenLocal() intellijPlatform { defaultRepositories() @@ -178,7 +178,7 @@ dependencies { // Coroutines must be excluded to avoid a conflict with the version bundled with the IDE // See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#coroutinesLibraries - implementation(libs.apollo.gradle.plugin.external) { + implementation(libs.apollo.gradle.plugin) { exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core") } implementation(libs.apollo.ast) @@ -193,7 +193,10 @@ dependencies { implementation(libs.apollo.runtime) { exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core") } + implementation(libs.apollo.compiler) + runtimeOnly(libs.slf4j.simple) + testImplementation(libs.google.testparameterinjector) // Temporary workaround for https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1663 diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCodegenService.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCodegenService.kt index c481f94..607a51a 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCodegenService.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCodegenService.kt @@ -3,6 +3,7 @@ package com.apollographql.ijplugin.codegen import com.apollographql.ijplugin.gradle.CODEGEN_GRADLE_TASK_NAME import com.apollographql.ijplugin.gradle.GradleHasSyncedListener import com.apollographql.ijplugin.gradle.SimpleProgressListener +import com.apollographql.ijplugin.gradle.apolloKotlinProjectModelService import com.apollographql.ijplugin.gradle.getGradleRootPath import com.apollographql.ijplugin.gradle.runGradleBuild import com.apollographql.ijplugin.project.ApolloProjectListener @@ -12,6 +13,7 @@ import com.apollographql.ijplugin.settings.ProjectSettingsListener import com.apollographql.ijplugin.settings.ProjectSettingsState import com.apollographql.ijplugin.settings.projectSettingsState import com.apollographql.ijplugin.util.apolloGeneratedSourcesRoots +import com.apollographql.ijplugin.util.apolloKotlinService import com.apollographql.ijplugin.util.dispose import com.apollographql.ijplugin.util.isNotDisposed import com.apollographql.ijplugin.util.logd @@ -19,6 +21,7 @@ import com.apollographql.ijplugin.util.logw import com.apollographql.ijplugin.util.newDisposable import com.apollographql.ijplugin.util.runWriteActionInEdt import com.intellij.lang.jsgraphql.GraphQLFileType +import com.intellij.lang.jsgraphql.psi.GraphQLFile import com.intellij.openapi.Disposable import com.intellij.openapi.components.Service import com.intellij.openapi.editor.Document @@ -33,6 +36,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.util.CheckedDisposable import com.intellij.openapi.vfs.VfsUtil +import com.intellij.psi.PsiDocumentManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.gradle.tooling.CancellationTokenSource @@ -83,20 +87,19 @@ class ApolloCodegenService( private fun startOrStopCodegenObservers() { if (shouldTriggerCodegenAutomatically()) { - // To make the codegen more reactive, any touched GraphQL document will automatically be saved (thus triggering Gradle) + // To make the codegen more reactive, any touched GraphQL document will automatically be saved (thus triggering the codegen) // as soon as the current editor is changed. startObserveDocumentChanges() startObserveFileEditorChanges() - startContinuousGradleCodegen() + startCodegen() - // Since we rely on Gradle's continuous build, which is not re-triggered when Gradle build files change, observe that - // ourselves and restart the build when it happens. + // A Gradle sync is a good indicator that Gradle files have changed - trigger a codegen build when that happens. startObserveGradleHasSynced() } else { stopObserveDocumentChanges() stopObserveFileEditorChanges() - stopContinuousGradleCodegen() + stopCodegen() stopObserveGradleHasSynced() } } @@ -144,8 +147,11 @@ class ApolloCodegenService( fileEditorChangesDisposable = disposable project.messageBus.connect(disposable).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener { override fun selectionChanged(event: FileEditorManagerEvent) { - logd(event.newFile) + logd(event.newFile?.path) dirtyGqlDocument?.let { + val operationGraphQLFile = PsiDocumentManager.getInstance(project).getPsiFile(dirtyGqlDocument!!) as? GraphQLFile + val apolloKotlinService = operationGraphQLFile?.apolloKotlinService() + logd("apolloKotlinService=${apolloKotlinService?.id}") dirtyGqlDocument = null runWriteActionInEdt { try { @@ -156,6 +162,14 @@ class ApolloCodegenService( logw(e, "Failed to save document") } } + + if (apolloKotlinService?.hasCompilerOptions == true) { + // We can use the built-in Apollo compiler + ApolloCompilerHelper(project).generateSources(apolloKotlinService) + } else { + // Fall back to the Gradle codegen task + startGradleCodegen() + } } } }) @@ -167,7 +181,7 @@ class ApolloCodegenService( fileEditorChangesDisposable = null } - private fun startContinuousGradleCodegen() { + private fun startGradleCodegen() { logd() if (gradleCodegenCancellation != null) { @@ -178,7 +192,7 @@ class ApolloCodegenService( val modules = ModuleManager.getInstance(project).modules coroutineScope.launch { gradleCodegenCancellation = GradleConnector.newCancellationTokenSource() - logd("Start Gradle") + logd("Start Gradle codegen build") try { val cancellationToken = gradleCodegenCancellation!!.token() val gradleProjectPath = project.getGradleRootPath() @@ -186,10 +200,9 @@ class ApolloCodegenService( logw("Could not get Gradle root project path") return@launch } - runGradleBuild(project, gradleProjectPath) { - it.forTasks(CODEGEN_GRADLE_TASK_NAME) + runGradleBuild(project, gradleProjectPath) { buildLauncher -> + buildLauncher.forTasks(CODEGEN_GRADLE_TASK_NAME) .withCancellationToken(cancellationToken) - .addArguments("--continuous") .let { if (project.projectSettingsState.automaticCodegenAdditionalGradleJvmArguments.isNotEmpty()) { it.addJvmArguments(project.projectSettingsState.automaticCodegenAdditionalGradleJvmArguments.split(' ')) @@ -199,7 +212,7 @@ class ApolloCodegenService( } .addProgressListener(object : SimpleProgressListener() { override fun onSuccess() { - logd("Gradle build success, marking generated source roots as dirty") + logd("Gradle codegen build success, marking generated source roots as dirty") // Mark the generated sources dirty so the files are visible to the IDE val generatedSourceRoots = modules.flatMap { it.apolloGeneratedSourcesRoots() } logd("Mark dirty $generatedSourceRoots") @@ -207,16 +220,16 @@ class ApolloCodegenService( } }) } - logd("Gradle execution finished") + logd("Gradle codegen build finished") } catch (t: Throwable) { - logd(t, "Gradle execution failed") + logd(t, "Gradle codegen build failed") } finally { gradleCodegenCancellation = null } } } - private fun stopContinuousGradleCodegen() { + private fun stopCodegen() { logd() gradleCodegenCancellation?.cancel() gradleCodegenCancellation = null @@ -235,12 +248,22 @@ class ApolloCodegenService( project.messageBus.connect(disposable).subscribe(GradleHasSyncedListener.TOPIC, object : GradleHasSyncedListener { override fun gradleHasSynced() { logd() - stopContinuousGradleCodegen() - if (shouldTriggerCodegenAutomatically()) startContinuousGradleCodegen() + stopCodegen() + if (shouldTriggerCodegenAutomatically()) startCodegen() } }) } + private fun startCodegen() { + if (project.apolloKotlinProjectModelService.getApolloKotlinServices().any { it.hasCompilerOptions }) { + logd("Using Apollo compiler for codegen") + ApolloCompilerHelper(project).generateAllSources() + } else { + logd("Using Gradle codegen task") + startGradleCodegen() + } + } + private fun stopObserveGradleHasSynced() { logd() dispose(gradleHasSyncedDisposable) @@ -249,6 +272,6 @@ class ApolloCodegenService( override fun dispose() { logd("project=${project.name}") - stopContinuousGradleCodegen() + stopCodegen() } } diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCompilerHelper.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCompilerHelper.kt new file mode 100644 index 0000000..5d56997 --- /dev/null +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCompilerHelper.kt @@ -0,0 +1,238 @@ +@file:OptIn(ApolloInternal::class, ApolloExperimental::class) + +package com.apollographql.ijplugin.codegen + +import com.apollographql.apollo.annotations.ApolloExperimental +import com.apollographql.apollo.annotations.ApolloInternal +import com.apollographql.apollo.compiler.ApolloCompiler +import com.apollographql.apollo.compiler.ApolloCompilerPlugin +import com.apollographql.apollo.compiler.ApolloCompilerPluginEnvironment +import com.apollographql.apollo.compiler.EntryPoints +import com.apollographql.apollo.compiler.UsedCoordinates +import com.apollographql.apollo.compiler.toCodegenSchemaOptions +import com.apollographql.apollo.compiler.toInputFiles +import com.apollographql.apollo.compiler.toIrOperations +import com.apollographql.apollo.compiler.writeTo +import com.apollographql.ijplugin.gradle.ApolloKotlinService +import com.apollographql.ijplugin.gradle.ApolloKotlinService.Id +import com.apollographql.ijplugin.gradle.apolloKotlinProjectModelService +import com.apollographql.ijplugin.util.logd +import com.apollographql.ijplugin.util.logw +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VfsUtil +import java.io.File +import java.net.URLClassLoader +import java.util.ServiceLoader + +class ApolloCompilerHelper( + private val project: Project, +) { + fun generateAllSources() { + logd() + val allServices = project.apolloKotlinProjectModelService.getApolloKotlinServices() + val leafServices = allServices.filter { candidateService -> allServices.none { it.upstreamServiceIds.contains(candidateService.id) } } + val outputDirs = mutableSetOf() + for (service in leafServices) { + outputDirs.addAll(internalGenerateSources(service)) + } + VfsUtil.markDirtyAndRefresh(true, true, true, *outputDirs.toTypedArray()) + logd("Apollo compiler sources generated for ${leafServices.map { it.id }}") + } + + fun generateSources(service: ApolloKotlinService) { + val outputDirs = internalGenerateSources(service) + VfsUtil.markDirtyAndRefresh(true, true, true, *outputDirs.toTypedArray()) + } + + private fun internalGenerateSources(service: ApolloKotlinService): Set { + try { + logd("Running Apollo compiler for service ${service.id}") + + val schemaService = service.findSchemaService() + if (schemaService == null) { + logw("No schema service found for ${service.id}. Cannot generate sources.") + return emptySet() + } + + val codegenSchemaFile = File.createTempFile("codegenSchemaFile", null) + EntryPoints.buildCodegenSchema( + plugins = schemaService.loadPlugins(), + logger = logger, + arguments = schemaService.pluginArguments!!, + normalizedSchemaFiles = schemaService.schemaPaths.map { File(it) }.toInputFiles(), + codegenSchemaOptionsFile = schemaService.codegenSchemaOptionsFile!!, + codegenSchemaFile = codegenSchemaFile, + ) + + + val allUpstreamServiceIds = service.allUpstreamServiceIds() + if (allUpstreamServiceIds == null) { + logw("Failed to find upstream services for ${service.id}. Cannot generate sources.") + return emptySet() + } + val allDownstreamServiceIds = schemaService.allDownstreamServiceIds() + if (allDownstreamServiceIds == null) { + logw("Failed to find downstream services for ${service.id}. Cannot generate sources.") + return emptySet() + } + + val irOperationsById = mutableMapOf() + val allServiceIds = (allUpstreamServiceIds + service.id + allDownstreamServiceIds).distinct() + for (serviceId in allServiceIds) { + val service = service(serviceId)!! + val irOperationsFile = File.createTempFile("irOperationsFile", null) + EntryPoints.buildIr( + plugins = service.loadPlugins(), + logger = logger, + arguments = service.pluginArguments!!, + graphqlFiles = service.executableFiles().toInputFiles(), + codegenSchemaFiles = listOf(codegenSchemaFile).toInputFiles(), + upstreamIrOperations = irOperationsById.values.toInputFiles(), + irOptionsFile = service.irOptionsFile!!, + irOperationsFile = irOperationsFile, + ) + irOperationsById[service.id] = irOperationsFile + } + + val usedCoordinates: UsedCoordinates = irOperationsById.values.map { + it.toIrOperations().usedCoordinates + }.fold(UsedCoordinates()) { acc, element -> + acc.mergeWith(element) + } + val usedCoordinatesFile = File.createTempFile("usedCoordinates", null) + usedCoordinates.writeTo(usedCoordinatesFile) + + val upstreamMetadata = mutableListOf() + val outputDirs = mutableSetOf() + for (serviceId in allServiceIds) { + val service = service(serviceId)!! + val allDownstreamServiceIds = service.allDownstreamServiceIds() + if (allDownstreamServiceIds == null) { + logw("Failed to find downstream services for ${service.id}. Cannot generate sources.") + return outputDirs + } + val allUpstreamServiceIds = service.allUpstreamServiceIds() + if (allUpstreamServiceIds == null) { + logw("Failed to find upstream services for ${service.id}. Cannot generate sources.") + return outputDirs + } + + service.operationManifestFile!!.parentFile.mkdirs() + val metadataOutput = File.createTempFile("metadataOutput", null) + EntryPoints.buildSourcesFromIr( + plugins = service.loadPlugins(), + logger = logger, + arguments = service.pluginArguments!!, + codegenSchemas = listOf(codegenSchemaFile).toInputFiles(), + upstreamMetadata = upstreamMetadata.toInputFiles(), + irOperations = irOperationsById[service.id]!!, + usedCoordinates = usedCoordinatesFile, + codegenOptions = service.codegenOptionsFile!!, + operationManifest = service.operationManifestFile, + outputDirectory = service.codegenOutputDir!!, + metadataOutput = metadataOutput, + ) + upstreamMetadata.add(metadataOutput) + outputDirs.add(service.codegenOutputDir) + } + + for (serviceId in allServiceIds) { + val service = service(serviceId)!! + if (service.codegenSchemaOptionsFile!!.toCodegenSchemaOptions().generateDataBuilders) { + EntryPoints.buildDataBuilders( + plugins = service.loadPlugins(), + arguments = service.pluginArguments!!, + logger = logger, + codegenSchemas = listOf(codegenSchemaFile).toInputFiles(), + upstreamMetadatas = upstreamMetadata.toInputFiles(), + downstreamUsedCoordinates = usedCoordinatesFile, + codegenOptions = service.codegenOptionsFile!!, + outputDirectory = service.dataBuildersOutputDir!!, + ) + outputDirs.add(service.dataBuildersOutputDir) + } + } + + logd("Apollo compiler sources generated for service ${service.id} at ${service.codegenOutputDir}") + return outputDirs + } catch (e: Exception) { + logw(e, "Failed to generate sources for service ${service.id}") + } + return emptySet() + } + + private fun ApolloKotlinService.findSchemaService(): ApolloKotlinService? { + if (upstreamServiceIds.isEmpty()) return this + val upstreamService = service(upstreamServiceIds.first()) ?: return null + return upstreamService.findSchemaService() + } + + private fun ApolloKotlinService.allUpstreamServiceIds(): List? { + val allUpstreamServiceIds = mutableListOf() + for (upstreamServiceId in this.upstreamServiceIds) { + val upstreamService = service(upstreamServiceId) ?: return null + val upstreamServiceUpstreamServices = upstreamService.allUpstreamServiceIds() ?: return null + allUpstreamServiceIds.addAll(upstreamServiceUpstreamServices) + allUpstreamServiceIds.add(upstreamServiceId) + } + return allUpstreamServiceIds.distinct() + } + + private fun ApolloKotlinService.allDownstreamServiceIds(): List? { + val allDownstreamServiceIds = mutableListOf() + for (downstreamServiceId in this.downstreamServiceIds) { + val downstreamService = service(downstreamServiceId) ?: return null + val downstreamServiceDownstreamServices = downstreamService.allDownstreamServiceIds() ?: return null + allDownstreamServiceIds.add(downstreamServiceId) + allDownstreamServiceIds.addAll(downstreamServiceDownstreamServices) + } + return allDownstreamServiceIds.distinct() + } + + private fun ApolloKotlinService.executableFiles(): List { + val executableFiles = mutableListOf() + for (operationPath in operationPaths.map { File(it) }) { + for (file in operationPath.walk()) { + if (file.extension == "graphql") { + executableFiles.add(file) + } + } + } + return executableFiles + } + + private fun service(id: Id): ApolloKotlinService? = project.apolloKotlinProjectModelService.getApolloKotlinService(id) + + private val logger = object : ApolloCompiler.Logger { + override fun debug(message: String) { + logd("Apollo Compiler: $message") + } + + override fun info(message: String) { + logd("Apollo Compiler: $message") + } + + override fun warning(message: String) { + logw("Apollo Compiler: $message") + } + + override fun error(message: String) { + logw("Apollo Compiler: $message") + } + } + + private fun ApolloKotlinService.loadPlugins(): List { + val classLoader = URLClassLoader( + pluginDependencies!!.map { File(it).toURI().toURL() }.toTypedArray(), + ApolloCompilerPlugin::class.java.classLoader + ) + val plugins = ServiceLoader.load(ApolloCompilerPlugin::class.java, classLoader).toMutableList() + val pluginProviders = + @Suppress("DEPRECATION") + ServiceLoader.load(com.apollographql.apollo.compiler.ApolloCompilerPluginProvider::class.java, classLoader).toList() + for (pluginProvider in pluginProviders) { + plugins.add(pluginProvider.create(ApolloCompilerPluginEnvironment(pluginArguments!!, logger))) + } + return plugins + } +} diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinProjectModelService.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinProjectModelService.kt new file mode 100644 index 0000000..46d4224 --- /dev/null +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinProjectModelService.kt @@ -0,0 +1,575 @@ +@file:OptIn(ApolloInternal::class, ApolloExperimental::class) + +package com.apollographql.ijplugin.gradle + +import com.apollographql.apollo.annotations.ApolloExperimental +import com.apollographql.apollo.annotations.ApolloInternal +import com.apollographql.apollo.compiler.model.CompilationUnitModel +import com.apollographql.apollo.compiler.model.ProjectModel +import com.apollographql.apollo.compiler.model.toCompilationUnitModel +import com.apollographql.apollo.compiler.model.toProjectModel +import com.apollographql.apollo.compiler.toCodegenOptions +import com.apollographql.apollo.gradle.api.ApolloGradleToolingModel +import com.apollographql.apollo.tooling.model.TelemetryData +import com.apollographql.apollo.tooling.model.toTelemetryData +import com.apollographql.ijplugin.project.ApolloProjectListener +import com.apollographql.ijplugin.project.ApolloProjectService +import com.apollographql.ijplugin.project.apolloProjectService +import com.apollographql.ijplugin.settings.ProjectSettingsListener +import com.apollographql.ijplugin.settings.ProjectSettingsState +import com.apollographql.ijplugin.settings.projectSettingsState +import com.apollographql.ijplugin.telemetry.telemetryService +import com.apollographql.ijplugin.util.dispose +import com.apollographql.ijplugin.util.isNotDisposed +import com.apollographql.ijplugin.util.logd +import com.apollographql.ijplugin.util.logw +import com.apollographql.ijplugin.util.newDisposable +import com.apollographql.ijplugin.util.toAny +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.CheckedDisposable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.gradle.tooling.CancellationTokenSource +import org.gradle.tooling.GradleConnector +import org.gradle.tooling.model.GradleProject +import java.io.File + +const val GENERATE_PROJECT_MODEL_TASK_NAME = "generateApolloProjectModel" + +/** + * Manages the Apollo Kotlin 'project model' as configured in the project's Gradle build files. + * + * The model is fetched via Gradle and represented as a set of [ApolloKotlinService] instances in [apolloKotlinServices]. These are also + * persisted in the project settings as a cache mechanism. + */ +@Service(Service.Level.PROJECT) +class ApolloKotlinProjectModelService( + private val project: Project, + private val coroutineScope: CoroutineScope, +) : Disposable { + private var gradleHasSyncedDisposable: CheckedDisposable? = null + + private var fetchProjectModelTask: FetchProjectModelTask? = null + + private var apolloKotlinServices: Map = + project.projectSettingsState.apolloKotlinServices.associateBy { it.id } + + init { + logd("project=${project.name}") + startObserveApolloProject() + startOrStopObserveGradleHasSynced() + startOrAbortFetchProjectModel() + startObserveSettings() + + if (shouldFetchProjectModel()) { + // Contribute immediately, even though the ApolloKotlinServices are not available yet. They will be contributed later when available. + // This avoids falling back to the default schema discovery of the GraphQL plugin which can be problematic (see https://github.com/apollographql/apollo-kotlin/issues/6219) + project.messageBus.syncPublisher(ApolloKotlinServiceListener.TOPIC).apolloKotlinServicesAvailable() + } + } + + private fun startObserveApolloProject() { + logd() + project.messageBus.connect(this).subscribe(ApolloProjectListener.TOPIC, object : ApolloProjectListener { + override fun apolloProjectChanged(apolloVersion: ApolloProjectService.ApolloVersion) { + logd("apolloVersion=$apolloVersion") + startOrStopObserveGradleHasSynced() + startOrAbortFetchProjectModel() + } + }) + } + + private fun shouldFetchProjectModel() = project.apolloProjectService.apolloVersion.isAtLeastV4 && + project.projectSettingsState.contributeConfigurationToGraphqlPlugin + + private fun startOrStopObserveGradleHasSynced() { + logd() + if (shouldFetchProjectModel()) { + startObserveGradleHasSynced() + } else { + stopObserveGradleHasSynced() + } + } + + private fun startObserveGradleHasSynced() { + logd() + if (gradleHasSyncedDisposable.isNotDisposed()) { + logd("Already observing") + return + } + val disposable = newDisposable() + gradleHasSyncedDisposable = disposable + project.messageBus.connect(disposable).subscribe(GradleHasSyncedListener.TOPIC, object : GradleHasSyncedListener { + override fun gradleHasSynced() { + logd() + startOrAbortFetchProjectModel() + } + }) + } + + private fun stopObserveGradleHasSynced() { + logd() + dispose(gradleHasSyncedDisposable) + gradleHasSyncedDisposable = null + } + + private fun startObserveSettings() { + logd() + project.messageBus.connect(this).subscribe(ProjectSettingsListener.TOPIC, object : ProjectSettingsListener { + private var contributeConfigurationToGraphqlPlugin: Boolean = project.projectSettingsState.contributeConfigurationToGraphqlPlugin + + override fun settingsChanged(projectSettingsState: ProjectSettingsState) { + val contributeConfigurationToGraphqlPluginChanged = + contributeConfigurationToGraphqlPlugin != projectSettingsState.contributeConfigurationToGraphqlPlugin + contributeConfigurationToGraphqlPlugin = projectSettingsState.contributeConfigurationToGraphqlPlugin + logd("contributeConfigurationToGraphqlPluginChanged=$contributeConfigurationToGraphqlPluginChanged") + if (contributeConfigurationToGraphqlPluginChanged) { + startOrAbortFetchProjectModel() + } + } + }) + } + + fun triggerFetchProjectModel() { + logd() + startOrAbortFetchProjectModel() + } + + private fun startOrAbortFetchProjectModel() { + logd() + abortFetchProjectModel() + if (shouldFetchProjectModel()) { + fetchProjectModel() + } + } + + /** + * Fetch the Apollo Kotlin project model, either by fetching the JSON models generated by the Apollo Gradle plugin + * (`generateApolloProjectModel` task introduced in v5) or by fetching the Gradle tooling models (legacy). + * + * This populates [apolloKotlinServices]. + */ + private fun fetchProjectModel() { + logd() + + if (fetchProjectModelTask?.gradleCancellation != null) { + logd("Already running") + return + } + + fetchProjectModelTask = FetchProjectModelTask().also { coroutineScope.launch { it.run() } } + } + + private class CompilationUnitModelWithOptions( + val compilationUnitModel: CompilationUnitModel, + val codegenSchemaOptionsFile: File, + val irOptionsFile: File, + val codegenOptionsFile: File, + val codegenOutputDir: File, + val operationManifestFile: File, + val dataBuildersOutputDir: File, + ) + + private inner class FetchProjectModelTask : Runnable { + var abortRequested: Boolean = false + var gradleCancellation: CancellationTokenSource? = null + + override fun run() { + try { + doRun() + } finally { + fetchProjectModelTask = null + } + } + + private fun doRun() { + val allApolloGradleProjects = getAllApolloGradleProjects() + if (allApolloGradleProjects == null) { + logw("Failed to fetch Gradle project model, aborting") + return + } + val projectModelsFetched = fetchProjectModels(allApolloGradleProjects) + logd("projectModelsFetched=$projectModelsFetched") + if (!projectModelsFetched) { + logd("Failed to fetch project models, fall back to fetching tooling models") + fetchToolingModels(allApolloGradleProjects) + } + return + } + + /** + * Fetch the project model via JSON files generated by the Apollo Gradle plugin (the `generateApolloProjectModel` task). + * This is the preferred way, introduced in Apollo Kotlin v5, which is more efficient than fetching Gradle tooling models. + */ + private fun fetchProjectModels(allApolloGradleProjects: List): Boolean { + logd() + gradleCancellation = GradleConnector.newCancellationTokenSource() + logd("Start Gradle $GENERATE_PROJECT_MODEL_TASK_NAME task") + var projectModelsFetched = false + try { + val cancellationToken = gradleCancellation!!.token() + val gradleProjectPath = project.getGradleRootPath() + if (gradleProjectPath == null) { + logw("Could not get Gradle root project path") + return false + } + runGradleBuild(project, gradleProjectPath) { buildLauncher -> + buildLauncher.forTasks(GENERATE_PROJECT_MODEL_TASK_NAME) + .withCancellationToken(cancellationToken) + .addProgressListener(object : SimpleProgressListener() { + override fun onSuccess() { + logd("Gradle $GENERATE_PROJECT_MODEL_TASK_NAME task success, reading models") + val readProjectModels = readProjectModels(allApolloGradleProjects) + logd("readProjectModels=$readProjectModels") + if (!readProjectModels) { + logw("Failed to read project models, falling back to fetching tooling models") + fetchToolingModels(allApolloGradleProjects) + } else { + projectModelsFetched = true + } + } + }) + } + logd("Gradle $GENERATE_PROJECT_MODEL_TASK_NAME task finished") + } catch (t: Throwable) { + logw(t, "Gradle $GENERATE_PROJECT_MODEL_TASK_NAME task failed") + } finally { + gradleCancellation = null + } + return projectModelsFetched + } + + private fun readProjectModels(allApolloGradleProjects: List): Boolean { + val allCompilationUnitModels = mutableListOf() + val allTelemetryData = mutableListOf() + for (gradleProject in allApolloGradleProjects) { + val projectDirectory = gradleProject.projectDirectory + val projectModel = readProjectModel(projectDirectory) + if (projectModel == null) { + logw("Failed to read project model from $projectDirectory") + return false + } + + val telemetryData = readTelemetryData(projectDirectory) + if (telemetryData != null) { + allTelemetryData.add(telemetryData) + } else { + logw("Failed to read telemetry data from $projectDirectory") + } + + val compilationUnitModels = readCompilationUnitModels(projectDirectory, projectModel) + if (compilationUnitModels == null) { + logw("Failed to read compilation unit models from $projectDirectory") + return false + } + allCompilationUnitModels.addAll(compilationUnitModels) + } + val apolloKotlinServices = compilationUnitModelsToApolloKotlinServices(allCompilationUnitModels) + + project.telemetryService.telemetryProperties = allTelemetryData.flatMap { it.toTelemetryProperties() }.toSet() + + apolloKotlinServices.flatMap { it.toTelemetryProperties() }.toSet() + + saveApolloKotlinServices(apolloKotlinServices) + return true + } + + private fun readProjectModel(projectDirectory: File): ProjectModel? { + val projectModelFile = projectModelFile(projectDirectory) + if (!projectModelFile.exists()) { + logw("Project model file does not exist: $projectModelFile") + return null + } + return projectModelFile.toProjectModel() + } + + private fun readTelemetryData(projectDirectory: File): TelemetryData? { + val telemetryDataFile = telemetryDataFile(projectDirectory) + if (!telemetryDataFile.exists()) { + logw("Telemetry data file does not exist: $telemetryDataFile") + return null + } + return telemetryDataFile.toTelemetryData() + } + + private fun readCompilationUnitModels(projectDirectory: File, projectModel: ProjectModel): List? { + return projectModel.serviceNames.map { serviceName -> + val compilationUnitModelFile = compilationUnitModelFile(projectDirectory, serviceName).also { + if (!it.exists()) { + logw("Compilation unit model file does not exist: $it") + return@readCompilationUnitModels null + } + } + + val codegenSchemaOptionsFile = codegenSchemaOptionsFile(projectDirectory, serviceName).also { + if (!it.exists()) { + logw("Codegen schema options file does not exist: $it") + return@readCompilationUnitModels null + } + } + + val irOptionsFile = irOptionsFile(projectDirectory, serviceName).also { + if (!it.exists()) { + logw("IR options file does not exist: $it") + return@readCompilationUnitModels null + } + } + + val codegenOptionsFile = codegenOptionsFile(projectDirectory, serviceName).also { + if (!it.exists()) { + logw("Codegen options file does not exist: $it") + return@readCompilationUnitModels null + } + } + + CompilationUnitModelWithOptions( + compilationUnitModel = compilationUnitModelFile.toCompilationUnitModel(), + codegenSchemaOptionsFile = codegenSchemaOptionsFile, + irOptionsFile = irOptionsFile, + codegenOptionsFile = codegenOptionsFile, + codegenOutputDir = codegenOutputDir(projectDirectory, serviceName), + operationManifestFile = operationManifestFile(projectDirectory, serviceName), + dataBuildersOutputDir = dataBuildersOutputDir(projectDirectory, serviceName), + ) + } + } + + /** + * Fetch the project model via Gradle tooling models exposed by the Apollo Gradle plugin. + * This is the legacy (Apollo Kotlin v3 and v4) way which is not efficient because it needs to iterate over all Gradle projects. + */ + private fun fetchToolingModels(allApolloGradleProjects: List) { + logd() + val allToolingModels = allApolloGradleProjects.mapNotNull { gradleProject -> + if (isAbortRequested()) return + + gradleCancellation = GradleConnector.newCancellationTokenSource() + logd("Fetch tooling model for ${gradleProject.path}") + try { + getGradleModel(project, gradleProject.projectDirectory.canonicalPath) { + it.withCancellationToken(gradleCancellation!!.token()) + } + ?.takeIf { + val isCompatibleVersion = it.versionMajor == ApolloGradleToolingModel.VERSION_MAJOR + if (!isCompatibleVersion) { + logw("Incompatible version of Apollo Gradle plugin in module ${gradleProject.path}: ${it.versionMajor} != ${ApolloGradleToolingModel.VERSION_MAJOR}, ignoring") + } + isCompatibleVersion + } + } catch (t: Throwable) { + logw(t, "Couldn't fetch tooling model for ${gradleProject.path}") + null + } finally { + gradleCancellation = null + } + } + + logd("allToolingModels=$allToolingModels") + project.telemetryService.telemetryProperties = allToolingModels.flatMap { it.toTelemetryProperties() }.toSet() + if (isAbortRequested()) return + + val apolloKotlinServices = toolingModelsToApolloKotlinServices(allToolingModels) + saveApolloKotlinServices(apolloKotlinServices) + } + + private fun getAllApolloGradleProjects(): List? { + logd("Fetch Gradle project model") + gradleCancellation = GradleConnector.newCancellationTokenSource() + val gradleProjectPath = project.getGradleRootPath() + if (gradleProjectPath == null) { + logw("Could not get Gradle root project path") + return null + } + val rootGradleProject: GradleProject = try { + getGradleModel(project, gradleProjectPath) { + it.withCancellationToken(gradleCancellation!!.token()) + } + } catch (t: Throwable) { + logw(t, "Couldn't fetch Gradle project model") + null + } finally { + gradleCancellation = null + } ?: return null + project.telemetryService.gradleModuleCount = rootGradleProject.children.size + 1 + + // We're only interested in projects that apply the Apollo plugin - and thus have the codegen task registered + val allApolloGradleProjects: List = rootGradleProject.allChildrenRecursively() + .filter { gradleProject -> gradleProject.tasks.any { task -> task.name == CODEGEN_GRADLE_TASK_NAME } } + logd("allApolloGradleProjects=${allApolloGradleProjects.map { it.path }}") + + project.telemetryService.apolloKotlinModuleCount = allApolloGradleProjects.size + return allApolloGradleProjects + } + + private fun isAbortRequested(): Boolean { + if (abortRequested) { + logd("Aborted") + return true + } + try { + ProgressManager.checkCanceled() + } catch (@Suppress("IncorrectCancellationExceptionHandling") _: ProcessCanceledException) { + logd("Canceled by user") + return true + } + return false + } + } + + /** + * Compute the ApolloKotlinServices from service models, taking into account the dependencies between projects. + */ + private fun compilationUnitModelsToApolloKotlinServices(compilationUnitModels: List): List { + val projectServiceToApolloKotlinServices = mutableMapOf() + + fun getApolloKotlinService(projectPath: String, serviceName: String): ApolloKotlinService { + val key = "$projectPath/$serviceName" + return projectServiceToApolloKotlinServices.getOrPut(key) { + val compilationUnitModelWithOptions = + compilationUnitModels.first { it.compilationUnitModel.gradleProjectPath == projectPath && it.compilationUnitModel.serviceName == serviceName } + val compilationUnitModel = compilationUnitModelWithOptions.compilationUnitModel + val upstreamApolloKotlinServices = compilationUnitModel.upstreamGradleProjectPaths + .map { upstreamProjectPath -> getApolloKotlinService(upstreamProjectPath, serviceName) } + ApolloKotlinService( + gradleProjectPath = projectPath, + serviceName = serviceName, + schemaPaths = compilationUnitModel.schemaFiles.toList(), + allSchemaPaths = (compilationUnitModel.schemaFiles.toList() + + upstreamApolloKotlinServices.flatMap { it.allSchemaPaths }) + .distinct(), + operationPaths = compilationUnitModel.graphqlSrcDirs.toList(), + allOperationPaths = (compilationUnitModel.graphqlSrcDirs.toList() + + upstreamApolloKotlinServices.flatMap { it.allOperationPaths }) + .distinct(), + endpointUrl = compilationUnitModel.endpointUrl, + endpointHeaders = compilationUnitModel.endpointHeaders, + upstreamServiceIds = upstreamApolloKotlinServices.map { it.id }, + downstreamServiceIds = compilationUnitModel.downstreamGradleProjectPaths.map { downstreamProjectPath -> ApolloKotlinService.Id(downstreamProjectPath, serviceName) }, + useSemanticNaming = compilationUnitModelWithOptions.codegenOptionsFile.toCodegenOptions().useSemanticNaming ?: true, + + codegenSchemaOptionsFile = compilationUnitModelWithOptions.codegenSchemaOptionsFile, + irOptionsFile = compilationUnitModelWithOptions.irOptionsFile, + codegenOptionsFile = compilationUnitModelWithOptions.codegenOptionsFile, + pluginDependencies = compilationUnitModelWithOptions.compilationUnitModel.pluginDependencies, + pluginArguments = compilationUnitModelWithOptions.compilationUnitModel.pluginArguments.mapValues { (_, v) -> v.toAny() }, + + codegenOutputDir = compilationUnitModelWithOptions.codegenOutputDir, + operationManifestFile = compilationUnitModelWithOptions.operationManifestFile, + dataBuildersOutputDir = compilationUnitModelWithOptions.dataBuildersOutputDir, + ) + } + } + + val apolloKotlinServices = mutableListOf() + for (compilationUnitModel in compilationUnitModels) { + apolloKotlinServices += getApolloKotlinService(compilationUnitModel.compilationUnitModel.gradleProjectPath, compilationUnitModel.compilationUnitModel.serviceName) + } + logd("apolloKotlinServices=\n${apolloKotlinServices.joinToString(",\n")}") + return apolloKotlinServices + } + + /** + * Compute the ApolloKotlinServices from Gradle Tooling Models, taking into account the dependencies between projects. + */ + private fun toolingModelsToApolloKotlinServices(toolingModels: List): List { + val allKnownProjectPaths = toolingModels.map { it.projectPathCompat } + val projectServiceToApolloKotlinServices = mutableMapOf() + + fun getApolloKotlinService(projectPath: String, serviceName: String): ApolloKotlinService { + val key = "$projectPath/$serviceName" + return projectServiceToApolloKotlinServices.getOrPut(key) { + val toolingModel = toolingModels.first { it.projectPathCompat == projectPath } + val serviceInfo = toolingModel.serviceInfos.first { it.name == serviceName } + val upstreamApolloKotlinServices = serviceInfo.upstreamProjectPathsCompat(toolingModel) + // The tooling model for some upstream projects might not have been fetched successfully - filter them out + .filter { upstreamProjectPath -> upstreamProjectPath in allKnownProjectPaths } + .map { upstreamProjectPath -> getApolloKotlinService(upstreamProjectPath, serviceName) } + ApolloKotlinService( + gradleProjectPath = projectPath, + serviceName = serviceName, + schemaPaths = serviceInfo.schemaFiles.map { it.absolutePath }, + allSchemaPaths = (serviceInfo.schemaFiles.map { it.absolutePath } + + upstreamApolloKotlinServices.flatMap { it.allSchemaPaths }) + .distinct(), + operationPaths = serviceInfo.graphqlSrcDirs.map { it.absolutePath }, + allOperationPaths = (serviceInfo.graphqlSrcDirs.map { it.absolutePath } + + upstreamApolloKotlinServices.flatMap { it.allOperationPaths }) + .distinct(), + endpointUrl = serviceInfo.endpointUrlCompat(toolingModel), + endpointHeaders = serviceInfo.endpointHeadersCompat(toolingModel), + upstreamServiceIds = upstreamApolloKotlinServices.map { it.id }, + useSemanticNaming = serviceInfo.useSemanticNamingCompat(toolingModel), + ) + } + } + + val apolloKotlinServices = mutableListOf() + for (toolingModel in toolingModels) { + for (serviceInfo in toolingModel.serviceInfos) { + apolloKotlinServices += getApolloKotlinService(toolingModel.projectPathCompat, serviceInfo.name) + } + } + logd("apolloKotlinServices=\n${apolloKotlinServices.joinToString(",\n")}") + return apolloKotlinServices + } + + private fun saveApolloKotlinServices(apolloKotlinServices: List) { + this@ApolloKotlinProjectModelService.apolloKotlinServices = apolloKotlinServices.associateBy { it.id } + // Cache the ApolloKotlinServices into the project settings + project.projectSettingsState.apolloKotlinServices = apolloKotlinServices + + // Services are available, notify interested parties + project.messageBus.syncPublisher(ApolloKotlinServiceListener.TOPIC).apolloKotlinServicesAvailable() + } + + private fun abortFetchProjectModel() { + logd() + fetchProjectModelTask?.abortRequested = true + fetchProjectModelTask?.gradleCancellation?.cancel() + fetchProjectModelTask = null + } + + override fun dispose() { + logd("project=${project.name}") + abortFetchProjectModel() + } + + fun getApolloKotlinServices(): List { + return apolloKotlinServices.values.toList() + } + + fun getApolloKotlinService(id: ApolloKotlinService.Id): ApolloKotlinService? { + return apolloKotlinServices[id] + } +} + +private val ApolloGradleToolingModel.projectPathCompat: String + get() = if (versionMinor >= 3) { + projectPath + } else { + @Suppress("DEPRECATION") + projectName + } + +private fun ApolloGradleToolingModel.ServiceInfo.upstreamProjectPathsCompat(toolingModel: ApolloGradleToolingModel) = + if (toolingModel.versionMinor >= 3) { + upstreamProjectPaths + } else { + @Suppress("DEPRECATION") + upstreamProjects + } + +private fun ApolloGradleToolingModel.ServiceInfo.endpointUrlCompat(toolingModel: ApolloGradleToolingModel) = + if (toolingModel.versionMinor >= 1) endpointUrl else null + +private fun ApolloGradleToolingModel.ServiceInfo.endpointHeadersCompat(toolingModel: ApolloGradleToolingModel) = + if (toolingModel.versionMinor >= 1) endpointHeaders else null + +private fun ApolloGradleToolingModel.ServiceInfo.useSemanticNamingCompat(toolingModel: ApolloGradleToolingModel) = + if (toolingModel.versionMinor >= 4) useSemanticNaming else true + +val Project.apolloKotlinProjectModelService get() = service() diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinService.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinService.kt index b5fac88..24a6c17 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinService.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinService.kt @@ -4,6 +4,7 @@ import com.intellij.util.messages.Topic import com.intellij.util.xmlb.annotations.Attribute import com.intellij.util.xmlb.annotations.Transient import com.intellij.util.xmlb.annotations.XCollection +import java.io.File interface ApolloKotlinServiceListener { companion object { @@ -21,7 +22,7 @@ interface ApolloKotlinServiceListener { * These are built from the [com.apollographql.apollo.gradle.api.ApolloGradleToolingModel] and are used to configure the GraphQL plugin, * and are cached into the project settings. * - * @see com.apollographql.ijplugin.gradle.GradleToolingModelService + * @see com.apollographql.ijplugin.gradle.ApolloKotlinProjectModelService * @see com.apollographql.ijplugin.graphql.ApolloGraphQLConfigContributor * @see com.apollographql.ijplugin.settings.ProjectSettingsService */ @@ -35,9 +36,21 @@ data class ApolloKotlinService( @XCollection val schemaPaths: List = emptyList(), + /** + * Includes all schema paths, including those from upstream services. + */ + @XCollection + val allSchemaPaths: List = emptyList(), + @XCollection val operationPaths: List = emptyList(), + /** + * Includes all operation paths, including those from upstream services. + */ + @XCollection + val allOperationPaths: List = emptyList(), + @Attribute val endpointUrl: String? = null, @@ -47,8 +60,35 @@ data class ApolloKotlinService( @XCollection val upstreamServiceIds: List = emptyList(), + @XCollection + val downstreamServiceIds: List = emptyList(), + @Attribute val useSemanticNaming: Boolean = true, + + @Transient + val codegenSchemaOptionsFile: File? = null, + + @Transient + val irOptionsFile: File? = null, + + @Transient + val codegenOptionsFile: File? = null, + + @Transient + val pluginDependencies: Set? = null, + + @Transient + val pluginArguments: Map? = null, + + @Transient + val codegenOutputDir: File? = null, + + @Transient + val operationManifestFile: File? = null, + + @Transient + val dataBuildersOutputDir: File? = null, ) { data class Id( @Attribute @@ -73,4 +113,8 @@ data class ApolloKotlinService( val id: Id @Transient get() = Id(gradleProjectPath, serviceName) + + val hasCompilerOptions + @Transient + get() = codegenOptionsFile != null && irOptionsFile != null && codegenSchemaOptionsFile != null && operationManifestFile != null } diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/GradleToolingModelService.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/GradleToolingModelService.kt deleted file mode 100644 index 0c62d3d..0000000 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/GradleToolingModelService.kt +++ /dev/null @@ -1,326 +0,0 @@ -package com.apollographql.ijplugin.gradle - -import com.apollographql.apollo.gradle.api.ApolloGradleToolingModel -import com.apollographql.ijplugin.project.ApolloProjectListener -import com.apollographql.ijplugin.project.ApolloProjectService -import com.apollographql.ijplugin.project.apolloProjectService -import com.apollographql.ijplugin.settings.ProjectSettingsListener -import com.apollographql.ijplugin.settings.ProjectSettingsState -import com.apollographql.ijplugin.settings.projectSettingsState -import com.apollographql.ijplugin.telemetry.telemetryService -import com.apollographql.ijplugin.util.dispose -import com.apollographql.ijplugin.util.isNotDisposed -import com.apollographql.ijplugin.util.logd -import com.apollographql.ijplugin.util.logw -import com.apollographql.ijplugin.util.newDisposable -import com.intellij.openapi.Disposable -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.progress.ProcessCanceledException -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.project.Project -import com.intellij.openapi.project.guessProjectDir -import com.intellij.openapi.util.CheckedDisposable -import com.intellij.openapi.vfs.VfsUtilCore -import com.intellij.openapi.vfs.VirtualFileManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import org.gradle.tooling.CancellationTokenSource -import org.gradle.tooling.GradleConnector -import org.gradle.tooling.model.GradleProject -import java.io.File - -@Service(Service.Level.PROJECT) -class GradleToolingModelService( - private val project: Project, - private val coroutineScope: CoroutineScope, -) : Disposable { - private var gradleHasSyncedDisposable: CheckedDisposable? = null - - private var fetchToolingModelsTask: FetchToolingModelsTask? = null - - var apolloKotlinServices: List = project.projectSettingsState.apolloKotlinServices - private set - - init { - logd("project=${project.name}") - startObserveApolloProject() - startOrStopObserveGradleHasSynced() - startOrAbortFetchToolingModels() - startObserveSettings() - - if (shouldFetchToolingModels()) { - // Contribute immediately, even though the ApolloKotlinServices are not available yet. They will be contributed later when available. - // This avoids falling back to the default schema discovery of the GraphQL plugin which can be problematic (see https://github.com/apollographql/apollo-kotlin/issues/6219) - project.messageBus.syncPublisher(ApolloKotlinServiceListener.TOPIC).apolloKotlinServicesAvailable() - } - } - - private fun startObserveApolloProject() { - logd() - project.messageBus.connect(this).subscribe(ApolloProjectListener.TOPIC, object : ApolloProjectListener { - override fun apolloProjectChanged(apolloVersion: ApolloProjectService.ApolloVersion) { - logd("apolloVersion=$apolloVersion") - startOrStopObserveGradleHasSynced() - startOrAbortFetchToolingModels() - } - }) - } - - private fun shouldFetchToolingModels() = project.apolloProjectService.apolloVersion.isAtLeastV4 && - project.projectSettingsState.contributeConfigurationToGraphqlPlugin - - private fun startOrStopObserveGradleHasSynced() { - logd() - if (shouldFetchToolingModels()) { - startObserveGradleHasSynced() - } else { - stopObserveGradleHasSynced() - } - } - - private fun startObserveGradleHasSynced() { - logd() - if (gradleHasSyncedDisposable.isNotDisposed()) { - logd("Already observing") - return - } - val disposable = newDisposable() - gradleHasSyncedDisposable = disposable - project.messageBus.connect(disposable).subscribe(GradleHasSyncedListener.TOPIC, object : GradleHasSyncedListener { - override fun gradleHasSynced() { - logd() - startOrAbortFetchToolingModels() - } - }) - } - - private fun stopObserveGradleHasSynced() { - logd() - dispose(gradleHasSyncedDisposable) - gradleHasSyncedDisposable = null - } - - private fun startObserveSettings() { - logd() - project.messageBus.connect(this).subscribe(ProjectSettingsListener.TOPIC, object : ProjectSettingsListener { - private var contributeConfigurationToGraphqlPlugin: Boolean = project.projectSettingsState.contributeConfigurationToGraphqlPlugin - - override fun settingsChanged(projectSettingsState: ProjectSettingsState) { - val contributeConfigurationToGraphqlPluginChanged = - contributeConfigurationToGraphqlPlugin != projectSettingsState.contributeConfigurationToGraphqlPlugin - contributeConfigurationToGraphqlPlugin = projectSettingsState.contributeConfigurationToGraphqlPlugin - logd("contributeConfigurationToGraphqlPluginChanged=$contributeConfigurationToGraphqlPluginChanged") - if (contributeConfigurationToGraphqlPluginChanged) { - startOrAbortFetchToolingModels() - } - } - }) - } - - fun triggerFetchToolingModels() { - logd() - startOrAbortFetchToolingModels() - } - - private fun startOrAbortFetchToolingModels() { - logd() - abortFetchToolingModels() - if (shouldFetchToolingModels()) { - fetchToolingModels() - } - } - - private fun fetchToolingModels() { - logd() - - if (fetchToolingModelsTask?.gradleCancellation != null) { - logd("Already running") - return - } - - fetchToolingModelsTask = FetchToolingModelsTask().also { coroutineScope.launch { it.run() } } - } - - private inner class FetchToolingModelsTask : Runnable { - var abortRequested: Boolean = false - var gradleCancellation: CancellationTokenSource? = null - - override fun run() { - try { - doRun() - } finally { - fetchToolingModelsTask = null - } - } - - private fun doRun() { - logd() - gradleCancellation = GradleConnector.newCancellationTokenSource() - logd("Fetch Gradle project model") - val gradleProjectPath = project.getGradleRootPath() - if (gradleProjectPath == null) { - logw("Could not get Gradle root project path") - return - } - val rootGradleProject: GradleProject = try { - getGradleModel(project, gradleProjectPath) { - it.withCancellationToken(gradleCancellation!!.token()) - } - } catch (t: Throwable) { - logw(t, "Couldn't fetch Gradle project model") - null - } finally { - gradleCancellation = null - } ?: return - project.telemetryService.gradleModuleCount = rootGradleProject.children.size + 1 - - // We're only interested in projects that apply the Apollo plugin - and thus have the codegen task registered - val allApolloGradleProjects: List = rootGradleProject.allChildrenRecursively() - .filter { gradleProject -> gradleProject.tasks.any { task -> task.name == CODEGEN_GRADLE_TASK_NAME } } - logd("allApolloGradleProjects=${allApolloGradleProjects.map { it.path }}") - project.telemetryService.apolloKotlinModuleCount = allApolloGradleProjects.size - - val allToolingModels = allApolloGradleProjects.mapIndexedNotNull { index, gradleProject -> - if (isAbortRequested()) return@doRun - - gradleCancellation = GradleConnector.newCancellationTokenSource() - logd("Fetch tooling model for ${gradleProject.path}") - try { - getGradleModel(project, gradleProject.projectDirectory.canonicalPath) { - it.withCancellationToken(gradleCancellation!!.token()) - } - ?.takeIf { - val isCompatibleVersion = it.versionMajor == ApolloGradleToolingModel.VERSION_MAJOR - if (!isCompatibleVersion) { - logw("Incompatible version of Apollo Gradle plugin in module ${gradleProject.path}: ${it.versionMajor} != ${ApolloGradleToolingModel.VERSION_MAJOR}, ignoring") - } - isCompatibleVersion - } - } catch (t: Throwable) { - logw(t, "Couldn't fetch tooling model for ${gradleProject.path}") - null - } finally { - gradleCancellation = null - } - } - - logd("allToolingModels=$allToolingModels") - if (isAbortRequested()) return - computeApolloKotlinServices(allToolingModels) - project.telemetryService.gradleToolingModels = allToolingModels.toSet() - } - - private fun isAbortRequested(): Boolean { - if (abortRequested) { - logd("Aborted") - return true - } - try { - ProgressManager.checkCanceled() - } catch (@Suppress("IncorrectCancellationExceptionHandling") _: ProcessCanceledException) { - logd("Canceled by user") - return true - } - return false - } - } - - private fun computeApolloKotlinServices(toolingModels: List) { - // Compute the ApolloKotlinServices, taking into account the dependencies between projects - val allKnownProjectPaths = toolingModels.map { it.projectPathCompat } - val projectServiceToApolloKotlinServices = mutableMapOf() - - fun getApolloKotlinService(projectPath: String, serviceName: String): ApolloKotlinService { - val key = "$projectPath/$serviceName" - return projectServiceToApolloKotlinServices.getOrPut(key) { - val toolingModel = toolingModels.first { it.projectPathCompat == projectPath } - val serviceInfo = toolingModel.serviceInfos.first { it.name == serviceName } - val upstreamApolloKotlinServices = serviceInfo.upstreamProjectPathsCompat(toolingModel) - // The tooling model for some upstream projects might not have been fetched successfully - filter them out - .filter { upstreamProjectPath -> upstreamProjectPath in allKnownProjectPaths } - .map { upstreamProjectPath -> getApolloKotlinService(upstreamProjectPath, serviceName) } - ApolloKotlinService( - gradleProjectPath = projectPath, - serviceName = serviceName, - schemaPaths = (serviceInfo.schemaFiles.mapNotNull { it.toProjectLocalPathOrNull() } + - upstreamApolloKotlinServices.flatMap { it.schemaPaths }) - .distinct(), - operationPaths = (serviceInfo.graphqlSrcDirs.mapNotNull { it.toProjectLocalPathOrNull() } + - upstreamApolloKotlinServices.flatMap { it.operationPaths }) - .distinct(), - endpointUrl = serviceInfo.endpointUrlCompat(toolingModel), - endpointHeaders = serviceInfo.endpointHeadersCompat(toolingModel), - upstreamServiceIds = upstreamApolloKotlinServices.map { it.id }, - useSemanticNaming = serviceInfo.useSemanticNamingCompat(toolingModel), - ) - } - } - - val apolloKotlinServices = mutableListOf() - for (toolingModel in toolingModels) { - for (serviceInfo in toolingModel.serviceInfos) { - apolloKotlinServices += getApolloKotlinService(toolingModel.projectPathCompat, serviceInfo.name) - } - } - logd("apolloKotlinServices=$apolloKotlinServices") - this.apolloKotlinServices = apolloKotlinServices - // Cache the ApolloKotlinServices into the project settings - project.projectSettingsState.apolloKotlinServices = apolloKotlinServices - - // Services are available, notify interested parties - project.messageBus.syncPublisher(ApolloKotlinServiceListener.TOPIC).apolloKotlinServicesAvailable() - } - - private fun File.toProjectLocalPathOrNull(): String? { - val projectDir = project.guessProjectDir() ?: return null - val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(toPath()) ?: return null - return VfsUtilCore.getRelativeLocation(virtualFile, projectDir) - } - - private fun abortFetchToolingModels() { - logd() - fetchToolingModelsTask?.abortRequested = true - fetchToolingModelsTask?.gradleCancellation?.cancel() - fetchToolingModelsTask = null - } - - override fun dispose() { - logd("project=${project.name}") - abortFetchToolingModels() - } - - companion object { - fun getApolloKotlinServices(project: Project): List { - if (!project.apolloProjectService.isInitialized) return emptyList() - return project.service().apolloKotlinServices - } - } -} - -private val ApolloGradleToolingModel.projectPathCompat: String - get() = if (versionMinor >= 3) { - projectPath - } else { - @Suppress("DEPRECATION") - projectName - } - -private fun ApolloGradleToolingModel.ServiceInfo.upstreamProjectPathsCompat(toolingModel: ApolloGradleToolingModel) = - if (toolingModel.versionMinor >= 3) { - upstreamProjectPaths - } else { - @Suppress("DEPRECATION") - upstreamProjects - } - -private fun ApolloGradleToolingModel.ServiceInfo.endpointUrlCompat(toolingModel: ApolloGradleToolingModel) = - if (toolingModel.versionMinor >= 1) endpointUrl else null - -private fun ApolloGradleToolingModel.ServiceInfo.endpointHeadersCompat(toolingModel: ApolloGradleToolingModel) = - if (toolingModel.versionMinor >= 1) endpointHeaders else null - -private fun ApolloGradleToolingModel.ServiceInfo.useSemanticNamingCompat(toolingModel: ApolloGradleToolingModel) = - if (toolingModel.versionMinor >= 4) useSemanticNaming else true - -val Project.gradleToolingModelService get() = service() diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/Paths.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/Paths.kt new file mode 100644 index 0000000..897b24e --- /dev/null +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/Paths.kt @@ -0,0 +1,31 @@ +package com.apollographql.ijplugin.gradle + +import com.apollographql.ijplugin.util.capitalizeFirstLetter +import java.io.File + +fun projectModelFile(projectDirectory: File) = + File(projectDirectory, "build/gtask/generateApolloProjectModel/projectModel") + +fun telemetryDataFile(projectDirectory: File) = + File(projectDirectory, "build/gtask/generateApolloProjectModel/telemetryData") + +fun compilationUnitModelFile(projectDirectory: File, serviceName: String) = + File(projectDirectory, "build/gtask/generate${serviceName.capitalizeFirstLetter()}ApolloCompilationUnitModel/compilationUnitModel") + +fun codegenSchemaOptionsFile(projectDirectory: File, serviceName: String) = + File(projectDirectory, "build/gtask/generate${serviceName.capitalizeFirstLetter()}ApolloOptions/codegenSchemaOptionsFile") + +fun irOptionsFile(projectDirectory: File, serviceName: String) = + File(projectDirectory, "build/gtask/generate${serviceName.capitalizeFirstLetter()}ApolloOptions/irOptionsFile") + +fun codegenOptionsFile(projectDirectory: File, serviceName: String) = + File(projectDirectory, "build/gtask/generate${serviceName.capitalizeFirstLetter()}ApolloOptions/codegenOptions") + +fun codegenOutputDir(projectDirectory: File, serviceName: String) = + File(projectDirectory, "build/generated/source/apollo/$serviceName") + +fun operationManifestFile(projectDirectory: File, serviceName: String) = + File(projectDirectory, "build/generated/manifest/apollo/$serviceName/persistedQueryManifest.json") + +fun dataBuildersOutputDir(projectDirectory: File, serviceName: String) = + File(projectDirectory, "build/generated/dataBuildersSource/apollo/$serviceName") diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/Telemetry.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/Telemetry.kt new file mode 100644 index 0000000..40d522f --- /dev/null +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/Telemetry.kt @@ -0,0 +1,143 @@ +package com.apollographql.ijplugin.gradle + +import com.apollographql.apollo.annotations.ApolloExperimental +import com.apollographql.apollo.annotations.ApolloInternal +import com.apollographql.apollo.compiler.TargetLanguage +import com.apollographql.apollo.compiler.toCodegenOptions +import com.apollographql.apollo.compiler.toCodegenSchemaOptions +import com.apollographql.apollo.compiler.toIrOptions +import com.apollographql.apollo.gradle.api.ApolloGradleToolingModel +import com.apollographql.apollo.tooling.model.TelemetryData +import com.apollographql.ijplugin.telemetry.TelemetryProperty +import com.apollographql.ijplugin.telemetry.TelemetryProperty.AndroidCompileSdk +import com.apollographql.ijplugin.telemetry.TelemetryProperty.AndroidGradlePluginVersion +import com.apollographql.ijplugin.telemetry.TelemetryProperty.AndroidMinSdk +import com.apollographql.ijplugin.telemetry.TelemetryProperty.AndroidTargetSdk +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloAddJvmOverloads +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloAddTypename +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloCodegenModels +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloDecapitalizeFields +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloFailOnWarnings +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloFieldsOnDisjointTypesMustMerge +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloFlattenModels +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateApolloMetadata +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateAsInternal +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateDataBuilders +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateFragmentImplementations +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateInputBuilders +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateKotlinModels +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateMethods +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateModelBuilders +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateOptionalOperationVariables +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGeneratePrimitiveTypes +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateQueryDocument +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateSchema +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateSourcesDuringGradleSync +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloJsExport +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloLanguageVersion +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloLinkSqlite +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloNullableFieldStyle +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloOperationManifestFormat +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloServiceCount +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloUseSemanticNaming +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloUsedOptions +import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloWarnOnDeprecatedUsages +import com.apollographql.ijplugin.telemetry.TelemetryProperty.GradleVersion +import com.apollographql.ijplugin.util.toCamelCase + +fun ApolloGradleToolingModel.toTelemetryProperties(): Set = buildSet { + // telemetryData was introduced in 1.2, accessing it on an older version will throw an exception + if (versionMajor == 1 && versionMinor < 2) return@buildSet + with(telemetryData) { + gradleVersion?.let { add(GradleVersion(it)) } + + androidMinSdk?.let { add(AndroidMinSdk(it)) } + androidTargetSdk?.let { add(AndroidTargetSdk(it)) } + androidCompileSdk?.let { add(AndroidCompileSdk(it)) } + androidAgpVersion?.let { add(AndroidGradlePluginVersion(it)) } + + apolloGenerateSourcesDuringGradleSync?.let { add(ApolloGenerateSourcesDuringGradleSync(it)) } + apolloLinkSqlite?.let { add(ApolloLinkSqlite(it)) } + add(ApolloServiceCount(apolloServiceCount)) + + apolloServiceTelemetryData.forEach { + it.codegenModels?.let { add(ApolloCodegenModels(it)) } + it.operationManifestFormat?.let { add(ApolloOperationManifestFormat(it)) } + it.warnOnDeprecatedUsages?.let { add(ApolloWarnOnDeprecatedUsages(it)) } + it.failOnWarnings?.let { add(ApolloFailOnWarnings(it)) } + it.generateKotlinModels?.let { add(ApolloGenerateKotlinModels(it)) } + it.languageVersion?.let { add(ApolloLanguageVersion(it)) } + it.useSemanticNaming?.let { add(ApolloUseSemanticNaming(it)) } + it.addJvmOverloads?.let { add(ApolloAddJvmOverloads(it)) } + it.generateAsInternal?.let { add(ApolloGenerateAsInternal(it)) } + it.generateFragmentImplementations?.let { add(ApolloGenerateFragmentImplementations(it)) } + it.generateQueryDocument?.let { add(ApolloGenerateQueryDocument(it)) } + it.generateSchema?.let { add(ApolloGenerateSchema(it)) } + it.generateOptionalOperationVariables?.let { add(ApolloGenerateOptionalOperationVariables(it)) } + it.generateDataBuilders?.let { add(ApolloGenerateDataBuilders(it)) } + it.generateModelBuilders?.let { add(ApolloGenerateModelBuilders(it)) } + it.generateMethods?.let { add(ApolloGenerateMethods(it)) } + it.generatePrimitiveTypes?.let { add(ApolloGeneratePrimitiveTypes(it)) } + it.generateInputBuilders?.let { add(ApolloGenerateInputBuilders(it)) } + it.nullableFieldStyle?.let { add(ApolloNullableFieldStyle(it)) } + it.decapitalizeFields?.let { add(ApolloDecapitalizeFields(it)) } + it.jsExport?.let { add(ApolloJsExport(it)) } + it.addTypename?.let { add(ApolloAddTypename(it)) } + it.flattenModels?.let { add(ApolloFlattenModels(it)) } + it.fieldsOnDisjointTypesMustMerge?.let { add(ApolloFieldsOnDisjointTypesMustMerge(it)) } + it.generateApolloMetadata?.let { add(ApolloGenerateApolloMetadata(it)) } + add(ApolloUsedOptions(it.usedOptions.toList())) + } + } +} + +@OptIn(ApolloInternal::class) +fun TelemetryData.toTelemetryProperties(): Set = buildSet { + gradleVersion?.let { add(GradleVersion(it)) } + + androidMinSdk?.let { add(AndroidMinSdk(it)) } + androidTargetSdk?.let { add(AndroidTargetSdk(it)) } + androidCompileSdk?.let { add(AndroidCompileSdk(it)) } + androidAgpVersion?.let { add(AndroidGradlePluginVersion(it)) } + + apolloGenerateSourcesDuringGradleSync?.let { add(ApolloGenerateSourcesDuringGradleSync(it)) } + apolloLinkSqlite?.let { add(ApolloLinkSqlite(it)) } + add(ApolloUsedOptions(usedServiceOptions.toList())) +} + +@OptIn(ApolloExperimental::class) +fun ApolloKotlinService.toTelemetryProperties(): Set { + val irOptions = irOptionsFile!!.toIrOptions() + val codegenOptions = codegenOptionsFile!!.toCodegenOptions() + val codegenSchemaOptions = codegenSchemaOptionsFile!!.toCodegenSchemaOptions() + return buildSet { + irOptions.codegenModels?.let { add(ApolloCodegenModels(it)) } + codegenOptions.operationManifestFormat?.let { add(ApolloOperationManifestFormat(it)) } + irOptions.warnOnDeprecatedUsages?.let { add(ApolloWarnOnDeprecatedUsages(it)) } + irOptions.failOnWarnings?.let { add(ApolloFailOnWarnings(it)) } + codegenOptions.targetLanguage?.let { add(ApolloGenerateKotlinModels(it != TargetLanguage.JAVA)) } + codegenOptions.targetLanguage?.let { + if (it.name.startsWith("KOTLIN_")) { + add(ApolloLanguageVersion(it.name.removePrefix("KOTLIN_").replace("_", "."))) + } + } + codegenOptions.useSemanticNaming?.let { add(ApolloUseSemanticNaming(it)) } + codegenOptions.addJvmOverloads?.let { add(ApolloAddJvmOverloads(it)) } + codegenOptions.generateAsInternal?.let { add(ApolloGenerateAsInternal(it)) } + codegenOptions.generateFragmentImplementations?.let { add(ApolloGenerateFragmentImplementations(it)) } + codegenOptions.generateQueryDocument?.let { add(ApolloGenerateQueryDocument(it)) } + codegenOptions.generateSchema?.let { add(ApolloGenerateSchema(it)) } + irOptions.generateOptionalOperationVariables?.let { add(ApolloGenerateOptionalOperationVariables(it)) } + add(ApolloGenerateDataBuilders(codegenSchemaOptions.generateDataBuilders)) + codegenOptions.generateModelBuilders?.let { add(ApolloGenerateModelBuilders(it)) } + codegenOptions.generateMethods?.let { add(ApolloGenerateMethods(it.map { it.name.toCamelCase() })) } + codegenOptions.generatePrimitiveTypes?.let { add(ApolloGeneratePrimitiveTypes(it)) } + codegenOptions.generateInputBuilders?.let { add(ApolloGenerateInputBuilders(it)) } + codegenOptions.nullableFieldStyle?.let { add(ApolloNullableFieldStyle(it.name.toCamelCase())) } + codegenOptions.decapitalizeFields?.let { add(ApolloDecapitalizeFields(it)) } + codegenOptions.jsExport?.let { add(ApolloJsExport(it)) } + irOptions.addTypename?.let { add(ApolloAddTypename(it)) } + irOptions.flattenModels?.let { add(ApolloFlattenModels(it)) } + irOptions.fieldsOnDisjointTypesMustMerge?.let { add(ApolloFieldsOnDisjointTypesMustMerge(it)) } + } +} diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/graphql/ApolloGraphQLConfigContributor.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/graphql/ApolloGraphQLConfigContributor.kt index 6be9fad..df16be8 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/graphql/ApolloGraphQLConfigContributor.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/graphql/ApolloGraphQLConfigContributor.kt @@ -1,7 +1,7 @@ package com.apollographql.ijplugin.graphql import com.apollographql.ijplugin.gradle.ApolloKotlinService -import com.apollographql.ijplugin.gradle.GradleToolingModelService +import com.apollographql.ijplugin.gradle.apolloKotlinProjectModelService import com.apollographql.ijplugin.project.apolloProjectService import com.apollographql.ijplugin.settings.projectSettingsState import com.apollographql.ijplugin.util.logd @@ -11,9 +11,11 @@ import com.intellij.lang.jsgraphql.ide.config.loader.GraphQLRawConfig import com.intellij.lang.jsgraphql.ide.config.loader.GraphQLRawProjectConfig import com.intellij.lang.jsgraphql.ide.config.loader.GraphQLRawSchemaPointer import com.intellij.lang.jsgraphql.ide.config.model.GraphQLConfig -import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFileManager +import java.io.File class ApolloGraphQLConfigContributor : GraphQLConfigContributor { override fun contributeConfigs(project: Project): Collection { @@ -30,17 +32,17 @@ class ApolloGraphQLConfigContributor : GraphQLConfigContributor { dir = projectDir, file = null, rawData = GraphQLRawConfig( - projects = project.service().apolloKotlinServices.associate { apolloKotlinService -> - apolloKotlinService.id.toString() to apolloKotlinService.toGraphQLRawProjectConfig() + projects = project.apolloKotlinProjectModelService.getApolloKotlinServices().associate { apolloKotlinService -> + apolloKotlinService.id.toString() to apolloKotlinService.toGraphQLRawProjectConfig(project) } ) ) ) } - private fun ApolloKotlinService.toGraphQLRawProjectConfig() = GraphQLRawProjectConfig( - schema = schemaPaths.map { GraphQLRawSchemaPointer(it) }, - include = operationPaths.map { "$it/**/*.graphql" }, + private fun ApolloKotlinService.toGraphQLRawProjectConfig(project: Project) = GraphQLRawProjectConfig( + schema = allSchemaPaths.map { GraphQLRawSchemaPointer(it.toProjectRelativePath(project)) }, + include = allOperationPaths.map { "${it.toProjectRelativePath(project)}/**/*.graphql" }, extensions = mapOf(EXTENSION_APOLLO_KOTLIN_SERVICE_ID to this.id.toString()) + (endpointUrl?.let { mapOf( @@ -60,3 +62,9 @@ class ApolloGraphQLConfigContributor : GraphQLConfigContributor { const val EXTENSION_APOLLO_KOTLIN_SERVICE_ID = "apolloKotlinServiceId" } } + +private fun String.toProjectRelativePath(project: Project): String { + val projectDir = project.guessProjectDir() ?: return "" + val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(File(this).toPath()) ?: return "" + return VfsUtilCore.getRelativeLocation(virtualFile, projectDir) ?: "" +} diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/inspection/ApolloMissingGraphQLDefinitionImportInspection.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/inspection/ApolloMissingGraphQLDefinitionImportInspection.kt index da806c5..74f7c94 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/inspection/ApolloMissingGraphQLDefinitionImportInspection.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/inspection/ApolloMissingGraphQLDefinitionImportInspection.kt @@ -11,7 +11,7 @@ import com.apollographql.apollo.ast.GQLNamed import com.apollographql.apollo.ast.GQLScalarTypeDefinition import com.apollographql.apollo.ast.rawType import com.apollographql.ijplugin.ApolloBundle -import com.apollographql.ijplugin.gradle.gradleToolingModelService +import com.apollographql.ijplugin.gradle.apolloKotlinProjectModelService import com.apollographql.ijplugin.graphql.ForeignSchemas import com.apollographql.ijplugin.graphql.url import com.apollographql.ijplugin.project.apolloProjectService @@ -138,7 +138,7 @@ private class ImportDefinitionQuickFix( directory.add(it) // There's a new schema file, reload the configuration - project.gradleToolingModelService.triggerFetchToolingModels() + project.apolloKotlinProjectModelService.triggerFetchProjectModel() } } else { val addedElement = extraSchemaFile.addBefore(linkDirectiveSchemaExtension, extraSchemaFile.firstChild) diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/project/ApolloProjectManagerListener.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/project/ApolloProjectManagerListener.kt index 58b9cd9..53babee 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/project/ApolloProjectManagerListener.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/project/ApolloProjectManagerListener.kt @@ -2,7 +2,7 @@ package com.apollographql.ijplugin.project import com.apollographql.ijplugin.ApolloBundle import com.apollographql.ijplugin.codegen.ApolloCodegenService -import com.apollographql.ijplugin.gradle.GradleToolingModelService +import com.apollographql.ijplugin.gradle.ApolloKotlinProjectModelService import com.apollographql.ijplugin.graphql.GraphQLConfigService import com.apollographql.ijplugin.lsp.ApolloLspAppService import com.apollographql.ijplugin.lsp.ApolloLspProjectService @@ -45,7 +45,7 @@ class ApolloProjectActivity : ProjectActivity { if (isKotlinPluginPresent && isGradlePluginPresent) { project.service() project.service() - project.service() + project.service() project.service() project.service() project.service() diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/settings/ProjectSettingsService.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/settings/ProjectSettingsService.kt index 2de9185..dfc0f4f 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/settings/ProjectSettingsService.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/settings/ProjectSettingsService.kt @@ -145,7 +145,7 @@ interface ProjectSettingsState { /** * Cache of the ApolloKotlinServices constructed from the Gradle tooling models. - * @see com.apollographql.ijplugin.gradle.GradleToolingModelService + * @see com.apollographql.ijplugin.gradle.ApolloKotlinProjectModelService */ var apolloKotlinServices: List var lspPassPathToSuperGraphYaml: Boolean diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/settings/studio/ApiKeyDialog.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/settings/studio/ApiKeyDialog.kt index 64ce588..1edf6c4 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/settings/studio/ApiKeyDialog.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/settings/studio/ApiKeyDialog.kt @@ -2,7 +2,7 @@ package com.apollographql.ijplugin.settings.studio import com.apollographql.ijplugin.ApolloBundle import com.apollographql.ijplugin.gradle.ApolloKotlinService -import com.apollographql.ijplugin.gradle.GradleToolingModelService +import com.apollographql.ijplugin.gradle.apolloKotlinProjectModelService import com.apollographql.ijplugin.util.validationOnApplyNotBlank import com.intellij.openapi.observable.util.whenItemSelectedFromUi import com.intellij.openapi.observable.util.whenTextChanged @@ -106,11 +106,11 @@ class ApiKeyDialog( }.withPreferredWidth(450) private fun getGradleProjectNames(): List { - return GradleToolingModelService.getApolloKotlinServices(project).map { it.id.gradleProjectPath }.distinct().sorted() + return project.apolloKotlinProjectModelService.getApolloKotlinServices().map { it.id.gradleProjectPath }.distinct().sorted() } private fun getApolloKotlinServiceNames(gradleProjectName: String): List { - return GradleToolingModelService.getApolloKotlinServices(project) + return project.apolloKotlinProjectModelService.getApolloKotlinServices() .filter { it.id.gradleProjectPath == gradleProjectName } .map { it.id.serviceName } .sorted() diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/studio/fieldinsights/FieldInsightsService.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/studio/fieldinsights/FieldInsightsService.kt index cd56d36..3cc6240 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/studio/fieldinsights/FieldInsightsService.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/studio/fieldinsights/FieldInsightsService.kt @@ -4,7 +4,7 @@ import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.tooling.FieldInsights import com.apollographql.ijplugin.gradle.ApolloKotlinService import com.apollographql.ijplugin.gradle.ApolloKotlinServiceListener -import com.apollographql.ijplugin.gradle.GradleToolingModelService +import com.apollographql.ijplugin.gradle.apolloKotlinProjectModelService import com.apollographql.ijplugin.settings.ApolloKotlinServiceConfiguration import com.apollographql.ijplugin.settings.ProjectSettingsListener import com.apollographql.ijplugin.settings.ProjectSettingsState @@ -85,7 +85,7 @@ class FieldInsightsServiceImpl(private val project: Project) : FieldInsightsServ override fun fetchLatencies() { logd() - val apolloKotlinServices = GradleToolingModelService.getApolloKotlinServices(project) + val apolloKotlinServices = project.apolloKotlinProjectModelService.getApolloKotlinServices() val apolloKotlinServicesWithConfigurations: Map = apolloKotlinServices.associateWith { service -> project.projectSettingsState.apolloKotlinServiceConfigurations.firstOrNull { configuration -> @@ -135,7 +135,7 @@ class FieldInsightsServiceImpl(private val project: Project) : FieldInsightsServ return fieldLatenciesByService[serviceId] } // Try upstream services - val apolloKotlinService = GradleToolingModelService.getApolloKotlinServices(project).firstOrNull { it.id == serviceId } ?: return null + val apolloKotlinService = project.apolloKotlinProjectModelService.getApolloKotlinService(serviceId) ?: return null for (upstreamServiceId in apolloKotlinService.upstreamServiceIds) { return getFieldLatenciesForService(upstreamServiceId) ?: continue } diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/Dependency.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/Dependency.kt index 396f454..2bc0a43 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/Dependency.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/Dependency.kt @@ -1,13 +1,9 @@ package com.apollographql.ijplugin.telemetry import com.apollographql.ijplugin.util.MavenCoordinates -import com.apollographql.ijplugin.util.apollo2 -import com.apollographql.ijplugin.util.apollo3 -import com.apollographql.ijplugin.util.apollo4 -@Suppress("KotlinConstantConditions") fun MavenCoordinates.toTelemetryProperty(): TelemetryProperty? = when { - group == apollo2 || group == apollo3 || group == apollo4 -> TelemetryProperty.Dependency(group, artifact, version) + group.startsWith("com.apollographql") -> TelemetryProperty.Dependency(group, artifact, version) group == "org.jetbrains.kotlin" && artifact == "kotlin-stdlib" -> TelemetryProperty.KotlinVersion(version) group == "androidx.compose.runtime" && artifact == "runtime" -> TelemetryProperty.ComposeVersion(version) else -> null diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/TelemetryService.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/TelemetryService.kt index e654850..1b108f3 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/TelemetryService.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/TelemetryService.kt @@ -1,6 +1,5 @@ package com.apollographql.ijplugin.telemetry -import com.apollographql.apollo.gradle.api.ApolloGradleToolingModel import com.apollographql.ijplugin.ApolloBundle import com.apollographql.ijplugin.icons.ApolloIcons import com.apollographql.ijplugin.project.ApolloProjectListener @@ -11,47 +10,13 @@ import com.apollographql.ijplugin.settings.AppSettingsState import com.apollographql.ijplugin.settings.appSettingsState import com.apollographql.ijplugin.settings.projectSettingsState import com.apollographql.ijplugin.studio.fieldinsights.ApolloFieldInsightsInspection -import com.apollographql.ijplugin.telemetry.TelemetryProperty.AndroidCompileSdk -import com.apollographql.ijplugin.telemetry.TelemetryProperty.AndroidGradlePluginVersion -import com.apollographql.ijplugin.telemetry.TelemetryProperty.AndroidMinSdk -import com.apollographql.ijplugin.telemetry.TelemetryProperty.AndroidTargetSdk -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloAddJvmOverloads -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloAddTypename -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloCodegenModels -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloDecapitalizeFields -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloFailOnWarnings -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloFieldsOnDisjointTypesMustMerge -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloFlattenModels -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateApolloMetadata -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateAsInternal -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateDataBuilders -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateFragmentImplementations -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateInputBuilders -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateKotlinModels -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateMethods -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateModelBuilders -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateOptionalOperationVariables -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGeneratePrimitiveTypes -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateQueryDocument -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateSchema -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloGenerateSourcesDuringGradleSync import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloIjPluginAutomaticCodegenTriggering import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloIjPluginContributeConfigurationToGraphqlPlugin import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloIjPluginHasConfiguredGraphOsApiKeys import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloIjPluginHighLatencyFieldThreshold import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloIjPluginVersion -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloJsExport import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloKotlinModuleCount -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloLanguageVersion -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloLinkSqlite -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloNullableFieldStyle -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloOperationManifestFormat -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloServiceCount -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloUseSemanticNaming -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloUsedOptions -import com.apollographql.ijplugin.telemetry.TelemetryProperty.ApolloWarnOnDeprecatedUsages import com.apollographql.ijplugin.telemetry.TelemetryProperty.GradleModuleCount -import com.apollographql.ijplugin.telemetry.TelemetryProperty.GradleVersion import com.apollographql.ijplugin.telemetry.TelemetryProperty.IdeVersion import com.apollographql.ijplugin.util.NOTIFICATION_GROUP_ID_TELEMETRY import com.apollographql.ijplugin.util.cast @@ -87,9 +52,9 @@ class TelemetryService( private val project: Project, ) : Disposable { - var gradleToolingModels: Set = emptySet() var gradleModuleCount: Int? = null var apolloKotlinModuleCount: Int? = null + var telemetryProperties: Set = emptySet() private val telemetryEventList: TelemetryEventList = TelemetryEventList() @@ -145,7 +110,7 @@ class TelemetryService( private fun buildTelemetrySession(): TelemetrySession { val properties = buildSet { addAll(project.getLibraryMavenCoordinates().toTelemetryProperties()) - addAll(gradleToolingModels.flatMap { it.toTelemetryProperties() }) + addAll(telemetryProperties) gradleModuleCount?.let { add(GradleModuleCount(it)) } apolloKotlinModuleCount?.let { add(ApolloKotlinModuleCount(it)) } addAll(getIdeTelemetryProperties()) @@ -228,49 +193,3 @@ class TelemetryService( } val Project.telemetryService get() = service() - -private fun ApolloGradleToolingModel.toTelemetryProperties(): Set = buildSet { - // telemetryData was introduced in 1.2, accessing it on an older version will throw an exception - if (versionMajor == 1 && versionMinor < 2) return@buildSet - with(telemetryData) { - gradleVersion?.let { add(GradleVersion(it)) } - - androidMinSdk?.let { add(AndroidMinSdk(it)) } - androidTargetSdk?.let { add(AndroidTargetSdk(it)) } - androidCompileSdk?.let { add(AndroidCompileSdk(it)) } - androidAgpVersion?.let { add(AndroidGradlePluginVersion(it)) } - - apolloGenerateSourcesDuringGradleSync?.let { add(ApolloGenerateSourcesDuringGradleSync(it)) } - apolloLinkSqlite?.let { add(ApolloLinkSqlite(it)) } - add(ApolloServiceCount(apolloServiceCount)) - - apolloServiceTelemetryData.forEach { - it.codegenModels?.let { add(ApolloCodegenModels(it)) } - it.operationManifestFormat?.let { add(ApolloOperationManifestFormat(it)) } - it.warnOnDeprecatedUsages?.let { add(ApolloWarnOnDeprecatedUsages(it)) } - it.failOnWarnings?.let { add(ApolloFailOnWarnings(it)) } - it.generateKotlinModels?.let { add(ApolloGenerateKotlinModels(it)) } - it.languageVersion?.let { add(ApolloLanguageVersion(it)) } - it.useSemanticNaming?.let { add(ApolloUseSemanticNaming(it)) } - it.addJvmOverloads?.let { add(ApolloAddJvmOverloads(it)) } - it.generateAsInternal?.let { add(ApolloGenerateAsInternal(it)) } - it.generateFragmentImplementations?.let { add(ApolloGenerateFragmentImplementations(it)) } - it.generateQueryDocument?.let { add(ApolloGenerateQueryDocument(it)) } - it.generateSchema?.let { add(ApolloGenerateSchema(it)) } - it.generateOptionalOperationVariables?.let { add(ApolloGenerateOptionalOperationVariables(it)) } - it.generateDataBuilders?.let { add(ApolloGenerateDataBuilders(it)) } - it.generateModelBuilders?.let { add(ApolloGenerateModelBuilders(it)) } - it.generateMethods?.let { add(ApolloGenerateMethods(it)) } - it.generatePrimitiveTypes?.let { add(ApolloGeneratePrimitiveTypes(it)) } - it.generateInputBuilders?.let { add(ApolloGenerateInputBuilders(it)) } - it.nullableFieldStyle?.let { add(ApolloNullableFieldStyle(it)) } - it.decapitalizeFields?.let { add(ApolloDecapitalizeFields(it)) } - it.jsExport?.let { add(ApolloJsExport(it)) } - it.addTypename?.let { add(ApolloAddTypename(it)) } - it.flattenModels?.let { add(ApolloFlattenModels(it)) } - it.fieldsOnDisjointTypesMustMerge?.let { add(ApolloFieldsOnDisjointTypesMustMerge(it)) } - it.generateApolloMetadata?.let { add(ApolloGenerateApolloMetadata(it)) } - add(ApolloUsedOptions(it.usedOptions.toList())) - } - } -} diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/util/Files.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/util/Files.kt index 488f4ef..0b7c715 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/util/Files.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/util/Files.kt @@ -9,6 +9,7 @@ import com.intellij.psi.PsiManager import com.intellij.psi.search.FilenameIndex import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiUtilCore +import java.io.File import java.nio.file.Path fun Project.findPsiFilesByName(fileName: String, searchScope: GlobalSearchScope): List { @@ -20,6 +21,10 @@ fun Project.findPsiFileByPath(path: String): PsiFile? { return VirtualFileManager.getInstance().findFileByNioPath(Path.of(path))?.let { PsiManager.getInstance(this).findFile(it) } } +fun File.toVirtualFile(): VirtualFile? { + return VirtualFileManager.getInstance().findFileByNioPath(toPath()) +} + fun Project.findPsiFileByUrl(url: String): PsiFile? { return VirtualFileManager.getInstance().findFileByUrl(url)?.let { PsiManager.getInstance(this).findFile(it) } } diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/util/GraphQL.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/util/GraphQL.kt index 43e77b2..bddfb61 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/util/GraphQL.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/util/GraphQL.kt @@ -1,7 +1,7 @@ package com.apollographql.ijplugin.util import com.apollographql.ijplugin.gradle.ApolloKotlinService -import com.apollographql.ijplugin.gradle.gradleToolingModelService +import com.apollographql.ijplugin.gradle.apolloKotlinProjectModelService import com.apollographql.ijplugin.graphql.ApolloGraphQLConfigContributor import com.intellij.lang.jsgraphql.ide.config.GraphQLConfigProvider import com.intellij.lang.jsgraphql.psi.GraphQLDirective @@ -102,7 +102,7 @@ fun GraphQLElement.apolloKotlinService(): ApolloKotlinService? { val projectConfig = GraphQLConfigProvider.getInstance(project).resolveProjectConfig(containingFile) ?: return null val apolloKotlinServiceId = projectConfig.extensions[ApolloGraphQLConfigContributor.EXTENSION_APOLLO_KOTLIN_SERVICE_ID] as? String ?: return null - return project.gradleToolingModelService.apolloKotlinServices.firstOrNull { it.id.toString() == apolloKotlinServiceId } + return project.apolloKotlinProjectModelService.getApolloKotlinService(ApolloKotlinService.Id.fromString(apolloKotlinServiceId)!!) } fun GraphQLDirective.argumentValue(argumentName: String): GraphQLValue? = diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/util/Json.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/util/Json.kt new file mode 100644 index 0000000..f15c60d --- /dev/null +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/util/Json.kt @@ -0,0 +1,23 @@ +package com.apollographql.ijplugin.util + +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.longOrNull + +fun JsonElement.toAny(): Any? = when (this) { + is JsonObject -> this.mapValues { it.value.toAny() } + is JsonArray -> this.map { it.toAny() } + is JsonPrimitive -> { + when { + isString -> this.content + this is JsonNull -> null + else -> booleanOrNull ?: intOrNull ?: longOrNull ?: doubleOrNull ?: error("cannot decode $this") + } + } +} diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/util/String.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/util/String.kt index f096af3..71c810b 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/util/String.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/util/String.kt @@ -15,3 +15,5 @@ fun String.capitalizeFirstLetter() = replaceFirstChar { if (it.isLowerCase()) it fun String.decapitalizeFirstLetter() = replaceFirstChar { if (it.isUpperCase()) it.lowercase(Locale.ROOT) else it.toString() } fun String.urlEncoded(): String = URLEncoder.encode(this, "UTF-8") + +fun String.toCamelCase() = split("_").joinToString("") { it.capitalizeFirstLetter() }.decapitalizeFirstLetter() diff --git a/plugin/src/main/resources/messages/ApolloBundle.properties b/plugin/src/main/resources/messages/ApolloBundle.properties index 26dc0e3..1c104a5 100644 --- a/plugin/src/main/resources/messages/ApolloBundle.properties +++ b/plugin/src/main/resources/messages/ApolloBundle.properties @@ -83,10 +83,8 @@ ApolloV3ToV4MigrationProcessor.noUsage=No Apollo Android 3 references found in t settings.codegen.title=Code Generation settings.codegen.automaticCodegenTriggering.text=Automatic code generation -settings.codegen.automaticCodegenTriggering.comment=Enabling this setting will run Gradle in continuous mode, similarly to \ - ./gradlew generateApolloSources --continuous.
\ - This watches changes to your GraphQL files and re-generates models when they change. -settings.codegen.additionalGradleJvmArguments.text=Additional JVM arguments: +settings.codegen.automaticCodegenTriggering.comment=Re-generates the models when GraphQL files are modified. +settings.codegen.additionalGradleJvmArguments.text=Additional Gradle JVM arguments: settings.graphqlPlugin.title=GraphQL Plugin settings.graphqlPlugin.contributeConfigurationToGraphqlPlugin.text=Contribute configuration to the GraphQL plugin diff --git a/settings.gradle.kts b/settings.gradle.kts index 1bea8b7..9dd3e2d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,10 +3,10 @@ rootProject.name = "apollo-intellij-plugin" @Suppress("UnstableApiUsage") listOf(pluginManagement.repositories, dependencyResolutionManagement.repositories).forEach { it.apply { -// mavenLocal() // maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // maven("https://storage.googleapis.com/apollo-previews/m2/") mavenCentral() + mavenLocal() gradlePluginPortal() } }