From 9fd16c899e0a3200ae00091a8dd0dd2b34147053 Mon Sep 17 00:00:00 2001 From: BoD Date: Fri, 19 Sep 2025 09:47:59 +0200 Subject: [PATCH] Invoke ApolloCompilerHelper dynamically with project's dependencies --- gradle/libs.versions.toml | 2 +- plugin/build.gradle.kts | 2 +- .../ijplugin/codegen/ApolloCodegenService.kt | 11 ++- .../{ => helper}/ApolloCompilerHelper.kt | 2 +- .../helper/DynamicApolloCompilerHelper.kt | 74 +++++++++++++++++++ .../gradle/ApolloKotlinProjectModelService.kt | 6 ++ settings.gradle.kts | 2 +- 7 files changed, 91 insertions(+), 8 deletions(-) rename plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/{ => helper}/ApolloCompilerHelper.kt (99%) create mode 100644 plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/helper/DynamicApolloCompilerHelper.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8301477..0037117 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.8.0" -apollo = "5.0.0-alpha.2" +apollo = "5.0.0-alpha.3-SNAPSHOT" grammarkit-plugin = "2022.3.2.2" changelog-plugin = "2.2.1" sqlite-jdbc = "3.43.2.0" diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 3b991e6..bc96479 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -23,7 +23,7 @@ repositories { // maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // maven("https://storage.googleapis.com/apollo-previews/m2/") mavenCentral() -// mavenLocal() + mavenLocal() intellijPlatform { defaultRepositories() 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 607a51a..a249352 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCodegenService.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCodegenService.kt @@ -1,5 +1,6 @@ package com.apollographql.ijplugin.codegen +import com.apollographql.ijplugin.codegen.helper.DynamicApolloCompilerHelper import com.apollographql.ijplugin.gradle.CODEGEN_GRADLE_TASK_NAME import com.apollographql.ijplugin.gradle.GradleHasSyncedListener import com.apollographql.ijplugin.gradle.SimpleProgressListener @@ -163,9 +164,9 @@ class ApolloCodegenService( } } - if (apolloKotlinService?.hasCompilerOptions == true) { + if (apolloKotlinService?.hasCompilerOptions == true && project.apolloKotlinProjectModelService.apolloTasksDependencies != null) { // We can use the built-in Apollo compiler - ApolloCompilerHelper(project).generateSources(apolloKotlinService) + DynamicApolloCompilerHelper(project, project.apolloKotlinProjectModelService.apolloTasksDependencies!!).generateSources(apolloKotlinService) } else { // Fall back to the Gradle codegen task startGradleCodegen() @@ -255,9 +256,11 @@ class ApolloCodegenService( } private fun startCodegen() { - if (project.apolloKotlinProjectModelService.getApolloKotlinServices().any { it.hasCompilerOptions }) { + if (project.apolloKotlinProjectModelService.getApolloKotlinServices() + .any { it.hasCompilerOptions } && project.apolloKotlinProjectModelService.apolloTasksDependencies != null + ) { logd("Using Apollo compiler for codegen") - ApolloCompilerHelper(project).generateAllSources() + DynamicApolloCompilerHelper(project, project.apolloKotlinProjectModelService.apolloTasksDependencies!!).generateAllSources() } else { logd("Using Gradle codegen task") startGradleCodegen() diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCompilerHelper.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/helper/ApolloCompilerHelper.kt similarity index 99% rename from plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCompilerHelper.kt rename to plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/helper/ApolloCompilerHelper.kt index 5d56997..506c7d8 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCompilerHelper.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/helper/ApolloCompilerHelper.kt @@ -1,6 +1,6 @@ @file:OptIn(ApolloInternal::class, ApolloExperimental::class) -package com.apollographql.ijplugin.codegen +package com.apollographql.ijplugin.codegen.helper import com.apollographql.apollo.annotations.ApolloExperimental import com.apollographql.apollo.annotations.ApolloInternal diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/helper/DynamicApolloCompilerHelper.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/helper/DynamicApolloCompilerHelper.kt new file mode 100644 index 0000000..3192b71 --- /dev/null +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/helper/DynamicApolloCompilerHelper.kt @@ -0,0 +1,74 @@ +package com.apollographql.ijplugin.codegen.helper + +import com.apollographql.ijplugin.gradle.ApolloKotlinService +import com.apollographql.ijplugin.util.logw +import com.intellij.openapi.project.Project +import java.io.File +import java.net.URL +import java.net.URLClassLoader + +/** + * Invokes [ApolloCompilerHelper] via introspection and a dedicated classloader using the project's dependencies. + * This ensures the same version of the Apollo libraries as the project's are used to generate the code. + */ +class DynamicApolloCompilerHelper( + private val project: Project, + private val apolloTasksDependencies: Set, +) { + private lateinit var apolloCompilerHelperClass: Class<*> + private lateinit var instance: Any + + private fun init() { + if (this::instance.isInitialized && this::apolloCompilerHelperClass.isInitialized) return + val dependencies = apolloTasksDependencies.map { File(it.trim()).toURI().toURL() } + val classLoader = ChildFirstClassLoader(dependencies.toTypedArray(), ApolloCompilerHelper::class.java.classLoader) + apolloCompilerHelperClass = classLoader.loadClass(ApolloCompilerHelper::class.java.name) + instance = apolloCompilerHelperClass.getDeclaredConstructor(Project::class.java).newInstance(project) + } + + fun generateAllSources() { + try { + init() + val method = apolloCompilerHelperClass.getMethod("generateAllSources") + method.invoke(instance) + } catch (e: Exception) { + logw(e, "Failed to generate sources for all services") + } + } + + fun generateSources(service: ApolloKotlinService) { + try { + init() + val method = apolloCompilerHelperClass.getMethod("generateSources", ApolloKotlinService::class.java) + method.invoke(instance, service) + } catch (e: Exception) { + logw(e, "Failed to generate sources for service ${service.id}") + } + } +} + +private class ChildFirstClassLoader(urls: Array, parent: ClassLoader) : URLClassLoader(urls, parent) { + override fun loadClass(name: String, resolve: Boolean): Class<*> { + val loadedClass = findLoadedClass(name) + if (loadedClass != null) return loadedClass + + // Load the helper from this classloader, but read the classes in the parent + if (name.startsWith("com.apollographql.ijplugin.codegen.helper")) { + val resourceName = name.replace('.', '/') + ".class" + val bytes = parent.getResourceAsStream(resourceName)?.readBytes() + ?: throw ClassNotFoundException("Could not read $name from parent classloader") + val clazz = defineClass(name, bytes, 0, bytes.size) + if (resolve) resolveClass(clazz) + return clazz + } + + return try { + val clazz = findClass(name) + if (resolve) resolveClass(clazz) + clazz + } catch (_: ClassNotFoundException) { + // Fallback to parent + super.loadClass(name, resolve) + } + } +} diff --git a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinProjectModelService.kt b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinProjectModelService.kt index 46d4224..c47c323 100644 --- a/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinProjectModelService.kt +++ b/plugin/src/main/kotlin/com/apollographql/ijplugin/gradle/ApolloKotlinProjectModelService.kt @@ -59,6 +59,9 @@ class ApolloKotlinProjectModelService( private var apolloKotlinServices: Map = project.projectSettingsState.apolloKotlinServices.associateBy { it.id } + var apolloTasksDependencies: Set? = null + private set + init { logd("project=${project.name}") startObserveApolloProject() @@ -268,6 +271,9 @@ class ApolloKotlinProjectModelService( return false } allCompilationUnitModels.addAll(compilationUnitModels) + + // The apolloTasksDependencies should be the same for all projects + apolloTasksDependencies = projectModel.apolloTasksDependencies } val apolloKotlinServices = compilationUnitModelsToApolloKotlinServices(allCompilationUnitModels) diff --git a/settings.gradle.kts b/settings.gradle.kts index 3140647..9dd3e2d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,7 +6,7 @@ listOf(pluginManagement.repositories, dependencyResolutionManagement.repositorie // maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // maven("https://storage.googleapis.com/apollo-previews/m2/") mavenCentral() -// mavenLocal() + mavenLocal() gradlePluginPortal() } }