• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2020 Google LLC
3  * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.google.devtools.ksp.gradle
19 
20 import com.google.devtools.ksp.gradle.model.builder.KspModelBuilder
21 import org.gradle.api.Action
22 import org.gradle.api.Project
23 import org.gradle.api.Task
24 import org.gradle.api.UnknownTaskException
25 import org.gradle.api.artifacts.Configuration
26 import org.gradle.api.attributes.Attribute
27 import org.gradle.api.file.ConfigurableFileCollection
28 import org.gradle.api.file.FileCollection
29 import org.gradle.api.provider.ListProperty
30 import org.gradle.api.provider.Provider
31 import org.gradle.api.tasks.TaskProvider
32 import org.gradle.api.tasks.compile.JavaCompile
33 import org.gradle.language.jvm.tasks.ProcessResources
34 import org.gradle.process.CommandLineArgumentProvider
35 import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
36 import org.gradle.util.GradleVersion
37 import org.jetbrains.kotlin.config.ApiVersion
38 import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
39 import org.jetbrains.kotlin.gradle.internal.kapt.incremental.CLASS_STRUCTURE_ARTIFACT_TYPE
40 import org.jetbrains.kotlin.gradle.internal.kapt.incremental.ClasspathSnapshot
41 import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptClasspathChanges
42 import org.jetbrains.kotlin.gradle.internal.kapt.incremental.StructureTransformAction
43 import org.jetbrains.kotlin.gradle.internal.kapt.incremental.StructureTransformLegacyAction
44 import org.jetbrains.kotlin.gradle.plugin.CompilerPluginConfig
45 import org.jetbrains.kotlin.gradle.plugin.FilesSubpluginOption
46 import org.jetbrains.kotlin.gradle.plugin.InternalSubpluginOption
47 import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
48 import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationWithResources
49 import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin
50 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
51 import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
52 import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact
53 import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
54 import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
55 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation
56 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
57 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation
58 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation
59 import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
60 import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
61 import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool
62 import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile
63 import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
64 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
65 import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon
66 import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
67 import org.jetbrains.kotlin.incremental.ChangedFiles
68 import org.jetbrains.kotlin.incremental.isJavaFile
69 import org.jetbrains.kotlin.incremental.isKotlinFile
70 import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
71 import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
72 import java.io.File
73 import java.util.concurrent.Callable
74 import javax.inject.Inject
75 
76 class KspGradleSubplugin @Inject internal constructor(private val registry: ToolingModelBuilderRegistry) :
77     KotlinCompilerPluginSupportPlugin {
78     companion object {
79         const val KSP_PLUGIN_ID = "com.google.devtools.ksp.symbol-processing"
80         const val KSP_API_ID = "symbol-processing-api"
81         const val KSP_COMPILER_PLUGIN_ID = "symbol-processing"
82         const val KSP_COMPILER_PLUGIN_ID_NON_EMBEDDABLE = "symbol-processing-cmdline"
83         const val KSP_GROUP_ID = "com.google.devtools.ksp"
84         const val KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME = "kspPluginClasspath"
85         const val KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME_NON_EMBEDDABLE = "kspPluginClasspathNonEmbeddable"
86 
87         @JvmStatic
88         fun getKspOutputDir(project: Project, sourceSetName: String, target: String) =
89             File(project.project.buildDir, "generated/ksp/$target/$sourceSetName")
90 
91         @JvmStatic
92         fun getKspClassOutputDir(project: Project, sourceSetName: String, target: String) =
93             File(getKspOutputDir(project, sourceSetName, target), "classes")
94 
95         @JvmStatic
96         fun getKspJavaOutputDir(project: Project, sourceSetName: String, target: String) =
97             File(getKspOutputDir(project, sourceSetName, target), "java")
98 
99         @JvmStatic
100         fun getKspKotlinOutputDir(project: Project, sourceSetName: String, target: String) =
101             File(getKspOutputDir(project, sourceSetName, target), "kotlin")
102 
103         @JvmStatic
104         fun getKspResourceOutputDir(project: Project, sourceSetName: String, target: String) =
105             File(getKspOutputDir(project, sourceSetName, target), "resources")
106 
107         @JvmStatic
108         fun getKspCachesDir(project: Project, sourceSetName: String, target: String) =
109             File(project.project.buildDir, "kspCaches/$target/$sourceSetName")
110 
111         @JvmStatic
112         private fun getSubpluginOptions(
113             project: Project,
114             kspExtension: KspExtension,
115             classpath: Configuration,
116             sourceSetName: String,
117             target: String,
118             isIncremental: Boolean,
119             allWarningsAsErrors: Boolean,
120             commandLineArgumentProviders: ListProperty<CommandLineArgumentProvider>,
121             commonSources: List<File>,
122         ): List<SubpluginOption> {
123             val options = mutableListOf<SubpluginOption>()
124             options +=
125                 InternalSubpluginOption("classOutputDir", getKspClassOutputDir(project, sourceSetName, target).path)
126             options +=
127                 InternalSubpluginOption("javaOutputDir", getKspJavaOutputDir(project, sourceSetName, target).path)
128             options +=
129                 InternalSubpluginOption("kotlinOutputDir", getKspKotlinOutputDir(project, sourceSetName, target).path)
130             options += InternalSubpluginOption(
131                 "resourceOutputDir",
132                 getKspResourceOutputDir(project, sourceSetName, target).path
133             )
134             options += InternalSubpluginOption("cachesDir", getKspCachesDir(project, sourceSetName, target).path)
135             options += InternalSubpluginOption("kspOutputDir", getKspOutputDir(project, sourceSetName, target).path)
136             options += SubpluginOption("incremental", isIncremental.toString())
137             options += SubpluginOption(
138                 "incrementalLog",
139                 project.findProperty("ksp.incremental.log")?.toString() ?: "false"
140             )
141             options += InternalSubpluginOption("projectBaseDir", project.project.projectDir.canonicalPath)
142             options += SubpluginOption("allWarningsAsErrors", allWarningsAsErrors.toString())
143             options += FilesSubpluginOption("apclasspath", classpath.toList())
144             // Turn this on by default to work KT-30172 around. It is off by default in the compiler plugin.
145             options += SubpluginOption(
146                 "returnOkOnError",
147                 project.findProperty("ksp.return.ok.on.error")?.toString() ?: "true"
148             )
149             commonSources.ifNotEmpty {
150                 options += FilesSubpluginOption("commonSources", this)
151             }
152 
153             kspExtension.apOptions.forEach {
154                 options += SubpluginOption("apoption", "${it.key}=${it.value}")
155             }
156             options += SubpluginOption(
157                 "excludedProcessors",
158                 kspExtension.excludedProcessors.joinToString(":")
159             )
160             options += SubpluginOption(
161                 "mapAnnotationArgumentsInJava",
162                 project.findProperty("ksp.map.annotation.arguments.in.java")?.toString() ?: "false"
163             )
164             commandLineArgumentProviders.get().forEach {
165                 it.asArguments().forEach { argument ->
166                     if (!argument.matches(Regex("\\S+=\\S+"))) {
167                         throw IllegalArgumentException("KSP apoption does not match \\S+=\\S+: $argument")
168                     }
169                     options += SubpluginOption("apoption", argument)
170                 }
171             }
172             return options
173         }
174     }
175 
176     private lateinit var kspConfigurations: KspConfigurations
177 
178     override fun apply(target: Project) {
179         target.extensions.create("ksp", KspExtension::class.java)
180         kspConfigurations = KspConfigurations(target)
181         registry.register(KspModelBuilder())
182     }
183 
184     override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
185         val project = kotlinCompilation.target.project
186         val kspVersion = ApiVersion.parse(KSP_KOTLIN_BASE_VERSION)!!
187         val kotlinVersion = ApiVersion.parse(project.getKotlinPluginVersion())!!
188 
189         // Check version and show warning by default.
190         val noVersionCheck = project.findProperty("ksp.version.check")?.toString()?.toBoolean() == false
191         if (!noVersionCheck) {
192             if (kspVersion < kotlinVersion) {
193                 project.logger.warn(
194                     "ksp-$KSP_VERSION is too old for kotlin-$kotlinVersion. " +
195                         "Please upgrade ksp or downgrade kotlin-gradle-plugin to $KSP_KOTLIN_BASE_VERSION."
196                 )
197             }
198             if (kspVersion > kotlinVersion) {
199                 project.logger.warn(
200                     "ksp-$KSP_VERSION is too new for kotlin-$kotlinVersion. " +
201                         "Please upgrade kotlin-gradle-plugin to $KSP_KOTLIN_BASE_VERSION."
202                 )
203             }
204         }
205 
206         return true
207     }
208 
209     // TODO: to be future proof, protect with `synchronized`
210     // Map from default input source set to output source set
211     private val sourceSetMap: MutableMap<KotlinSourceSet, KotlinSourceSet> = mutableMapOf()
212 
213     override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider<List<SubpluginOption>> {
214         val project = kotlinCompilation.target.project
215         val kotlinCompileProvider: TaskProvider<AbstractKotlinCompileTool<*>> =
216             project.locateTask(kotlinCompilation.compileKotlinTaskName) ?: return project.provider { emptyList() }
217         val kspExtension = project.extensions.getByType(KspExtension::class.java)
218         val kspConfigurations = kspConfigurations.find(kotlinCompilation)
219         val nonEmptyKspConfigurations = kspConfigurations.filter { it.allDependencies.isNotEmpty() }
220         if (nonEmptyKspConfigurations.isEmpty()) {
221             return project.provider { emptyList() }
222         }
223         if (kotlinCompileProvider.name == "compileKotlinMetadata") {
224             return project.provider { emptyList() }
225         }
226 
227         val target = kotlinCompilation.target.name
228         val sourceSetName = kotlinCompilation.defaultSourceSet.name
229         val classOutputDir = getKspClassOutputDir(project, sourceSetName, target)
230         val javaOutputDir = getKspJavaOutputDir(project, sourceSetName, target)
231         val kotlinOutputDir = getKspKotlinOutputDir(project, sourceSetName, target)
232         val resourceOutputDir = getKspResourceOutputDir(project, sourceSetName, target)
233         val kspOutputDir = getKspOutputDir(project, sourceSetName, target)
234 
235         val kspClasspathCfg = project.configurations.maybeCreate(KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME)
236         project.dependencies.add(
237             KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME,
238             "$KSP_GROUP_ID:$KSP_API_ID:$KSP_VERSION"
239         )
240         project.dependencies.add(
241             KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME,
242             "$KSP_GROUP_ID:$KSP_COMPILER_PLUGIN_ID:$KSP_VERSION"
243         )
244 
245         val kspClasspathCfgNonEmbeddable = project.configurations.maybeCreate(
246             KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME_NON_EMBEDDABLE
247         )
248         project.dependencies.add(
249             KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME_NON_EMBEDDABLE,
250             "$KSP_GROUP_ID:$KSP_API_ID:$KSP_VERSION"
251         )
252         project.dependencies.add(
253             KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME_NON_EMBEDDABLE,
254             "$KSP_GROUP_ID:$KSP_COMPILER_PLUGIN_ID_NON_EMBEDDABLE:$KSP_VERSION"
255         )
256 
257         findJavaTaskForKotlinCompilation(kotlinCompilation)?.configure { javaCompile ->
258             val generatedJavaSources = javaCompile.project.fileTree(javaOutputDir)
259             generatedJavaSources.include("**/*.java")
260             javaCompile.source(generatedJavaSources)
261             javaCompile.classpath += project.files(classOutputDir)
262         }
263 
264         val processingModel = project.findProperty("ksp.experimental.processing.model")?.toString() ?: "traditional"
265 
266         assert(kotlinCompileProvider.name.startsWith("compile"))
267         val kspTaskName = kotlinCompileProvider.name.replaceFirst("compile", "ksp")
268 
269         val kspGeneratedSourceSet =
270             project.kotlinExtension.sourceSets.create("generatedBy" + kspTaskName.capitalizeAsciiOnly())
271         sourceSetMap.put(kotlinCompilation.defaultSourceSet, kspGeneratedSourceSet)
272 
273         val processorClasspath = project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath")
274             .extendsFrom(*nonEmptyKspConfigurations.toTypedArray())
275         fun configureAsKspTask(kspTask: KspTask, isIncremental: Boolean) {
276             // depends on the processor; if the processor changes, it needs to be reprocessed.
277             kspTask.dependsOn(processorClasspath.buildDependencies)
278             kspTask.commandLineArgumentProviders.addAll(kspExtension.commandLineArgumentProviders)
279 
280             val commonSources: List<File> = when (processingModel) {
281                 "hierarchical" -> {
282                     fun unclaimedDeps(roots: Set<KotlinSourceSet>): Set<KotlinSourceSet> {
283                         val unclaimedParents =
284                             roots.flatMap { it.dependsOn }.filterNot { it in sourceSetMap }.toSet()
285                         return if (unclaimedParents.isEmpty()) {
286                             unclaimedParents
287                         } else {
288                             unclaimedParents + unclaimedDeps(unclaimedParents)
289                         }
290                     }
291                     // Source sets that are not claimed by other compilations.
292                     // I.e., those that should be processed by this compilation.
293                     val unclaimed =
294                         kotlinCompilation.kotlinSourceSets + unclaimedDeps(kotlinCompilation.kotlinSourceSets)
295                     val commonSourceSets = kotlinCompilation.allKotlinSourceSets - unclaimed
296                     commonSourceSets.flatMap { it.kotlin.files }
297                 }
298                 else -> emptyList()
299             }
300 
301             kspTask.options.addAll(
302                 kspTask.project.provider {
303                     getSubpluginOptions(
304                         project,
305                         kspExtension,
306                         processorClasspath,
307                         sourceSetName,
308                         target,
309                         isIncremental,
310                         kspExtension.allWarningsAsErrors,
311                         kspTask.commandLineArgumentProviders,
312                         commonSources,
313                     )
314                 }
315             )
316             kspTask.inputs.property("apOptions", kspExtension.arguments)
317         }
318 
319         fun configureAsAbstractKotlinCompileTool(kspTask: AbstractKotlinCompileTool<*>) {
320             when (kspTask) {
321                 is Kotlin2JsCompile -> {
322                     kspTask.outputFileProperty.value(
323                         File(kspOutputDir, "dummyOutput.js")
324                     )
325                 }
326                 else -> kspTask.destinationDirectory.set(kspOutputDir)
327             }
328             kspTask.outputs.dirs(
329                 kotlinOutputDir,
330                 javaOutputDir,
331                 classOutputDir,
332                 resourceOutputDir
333             )
334 
335             val kotlinCompileTask = kotlinCompileProvider.get()
336             if (kspExtension.allowSourcesFromOtherPlugins) {
337                 fun FileCollection.nonSelfDeps(): List<Task> =
338                     buildDependencies.getDependencies(null).filterNot {
339                         it.name == kspTaskName
340                     }
341 
342                 fun setSource(source: FileCollection) {
343                     // kspTask.setSource(source) would create circular dependency.
344                     // Therefore we need to manually extract input deps, filter them, and tell kspTask.
345                     kspTask.setSource(project.provider { source.files })
346                     kspTask.dependsOn(project.provider { source.nonSelfDeps() })
347                 }
348 
349                 setSource(kotlinCompileTask.sources - kspGeneratedSourceSet.kotlin)
350                 if (kotlinCompileTask is KotlinCompile) {
351                     setSource(kotlinCompileTask.javaSources - kspGeneratedSourceSet.kotlin)
352                 }
353             } else {
354                 kotlinCompilation.allKotlinSourceSets.filterNot { it == kspGeneratedSourceSet }.forEach { sourceSet ->
355                     kspTask.setSource(sourceSet.kotlin)
356                 }
357                 if (kotlinCompilation is KotlinCommonCompilation) {
358                     kspTask.setSource(kotlinCompilation.defaultSourceSet.kotlin)
359                 }
360                 val generated = when (processingModel) {
361                     "hierarchical" -> {
362                         // boundary parent source sets that are going to be compiled by other compilations
363                         fun claimedParents(root: KotlinSourceSet): Set<KotlinSourceSet> {
364                             val (claimed, unclaimed) = root.dependsOn.partition { it in sourceSetMap }
365                             return claimed.toSet() + unclaimed.flatMap { claimedParents(it) }
366                         }
367                         kotlinCompilation.kotlinSourceSets.flatMap { claimedParents(it) }.map { sourceSetMap[it]!! }
368                     }
369                     else -> emptyList()
370                 }
371                 generated.forEach {
372                     kspTask.setSource(it.kotlin)
373                 }
374             }
375             kspTask.exclude { kspOutputDir.isParentOf(it.file) }
376 
377             kspTask.libraries.setFrom(
378                 kotlinCompileTask.project.files(
379                     Callable {
380                         kotlinCompileTask.libraries.filter {
381                             !kspOutputDir.isParentOf(it)
382                         }
383                     }
384                 )
385             )
386             // kotlinc's incremental compilation isn't compatible with symbol processing in a few ways:
387             // * It doesn't consider private / internal changes when computing dirty sets.
388             // * It compiles iteratively; Sources can be compiled in different rounds.
389             (kspTask as? AbstractKotlinCompile<*>)?.incremental = false
390         }
391 
392         fun maybeBlockOtherPlugins(kspTask: BaseKotlinCompile) {
393             if (kspExtension.blockOtherCompilerPlugins) {
394                 kspTask.pluginClasspath.setFrom(kspClasspathCfg)
395                 kspTask.pluginOptions.set(emptyList())
396             }
397         }
398 
399         fun configurePluginOptions(kspTask: BaseKotlinCompile) {
400             kspTask.pluginOptions.add(
401                 project.provider {
402                     CompilerPluginConfig().apply {
403                         (kspTask as KspTask).options.get().forEach {
404                             addPluginArgument(KSP_PLUGIN_ID, it)
405                         }
406                     }
407                 }
408             )
409         }
410 
411         val isIncremental = project.findProperty("ksp.incremental")?.toString()?.toBoolean() ?: true
412 
413         // Create and configure KSP tasks.
414         val kspTaskProvider = when (kotlinCompilation.platformType) {
415             KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
416                 kotlinCompilation as KotlinCompilationData<*>
417                 KotlinFactories.registerKotlinJvmCompileTask(project, kspTaskName, kotlinCompilation).also {
418                     it.configure { kspTask ->
419                         val kotlinCompileTask = kotlinCompileProvider.get() as KotlinCompile
420                         maybeBlockOtherPlugins(kspTask as BaseKotlinCompile)
421                         configureAsKspTask(kspTask, isIncremental)
422                         configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>)
423                         configurePluginOptions(kspTask)
424                         kspTask.compilerOptions.noJdk.value(kotlinCompileTask.compilerOptions.noJdk)
425                         kspTask.compilerOptions.useK2.value(false)
426                         kspTask.ownModuleName.value(kotlinCompileTask.ownModuleName.map { "$it-ksp" })
427                         kspTask.moduleName.value(kotlinCompileTask.moduleName.get())
428                         kspTask.destination.value(kspOutputDir)
429 
430                         val isIntermoduleIncremental =
431                             (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) &&
432                                 isIncremental
433                         val classStructureFiles = getClassStructureFiles(project, kspTask.libraries)
434                         kspTask.incrementalChangesTransformers.add(
435                             createIncrementalChangesTransformer(
436                                 isIncremental,
437                                 isIntermoduleIncremental,
438                                 getKspCachesDir(project, sourceSetName, target),
439                                 project.provider { classStructureFiles },
440                                 project.provider { kspTask.libraries },
441                                 project.provider { processorClasspath }
442                             )
443                         )
444                     }
445                     // Don't support binary generation for non-JVM platforms yet.
446                     // FIXME: figure out how to add user generated libraries.
447                     kotlinCompilation.output.classesDirs.from(classOutputDir)
448                 }
449             }
450             KotlinPlatformType.js, KotlinPlatformType.wasm -> {
451                 kotlinCompilation as KotlinCompilationData<*>
452                 KotlinFactories.registerKotlinJSCompileTask(project, kspTaskName, kotlinCompilation).also {
453                     it.configure { kspTask ->
454                         val kotlinCompileTask = kotlinCompileProvider.get() as Kotlin2JsCompile
455                         maybeBlockOtherPlugins(kspTask as BaseKotlinCompile)
456                         configureAsKspTask(kspTask, isIncremental)
457                         configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>)
458                         configurePluginOptions(kspTask)
459                         kspTask.compilerOptions.freeCompilerArgs
460                             .value(kotlinCompileTask.compilerOptions.freeCompilerArgs)
461                         kspTask.compilerOptions.useK2.value(false)
462                         kspTask.moduleName.value(kotlinCompileTask.moduleName.map { "$it-ksp" })
463 
464                         kspTask.incrementalChangesTransformers.add(
465                             createIncrementalChangesTransformer(
466                                 isIncremental,
467                                 false,
468                                 getKspCachesDir(project, sourceSetName, target),
469                                 project.provider { project.files() },
470                                 project.provider { project.files() },
471                                 project.provider { project.files() },
472                             )
473                         )
474                     }
475                 }
476             }
477             KotlinPlatformType.common -> {
478                 kotlinCompilation as KotlinCompilationData<*>
479                 KotlinFactories.registerKotlinMetadataCompileTask(project, kspTaskName, kotlinCompilation).also {
480                     it.configure { kspTask ->
481                         val kotlinCompileTask = kotlinCompileProvider.get() as KotlinCompileCommon
482                         maybeBlockOtherPlugins(kspTask as BaseKotlinCompile)
483                         configureAsKspTask(kspTask, isIncremental)
484                         configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>)
485                         configurePluginOptions(kspTask)
486                         kspTask.compilerOptions.useK2.value(false)
487 
488                         kspTask.incrementalChangesTransformers.add(
489                             createIncrementalChangesTransformer(
490                                 isIncremental,
491                                 false,
492                                 getKspCachesDir(project, sourceSetName, target),
493                                 project.provider { project.files() },
494                                 project.provider { project.files() },
495                                 project.provider { project.files() },
496                             )
497                         )
498                     }
499                 }
500             }
501             KotlinPlatformType.native -> {
502                 KotlinFactories.registerKotlinNativeCompileTask(project, kspTaskName, kotlinCompilation).also {
503                     it.configure { kspTask ->
504                         val kotlinCompileTask = kotlinCompileProvider.get() as KotlinNativeCompile
505                         configureAsKspTask(kspTask, false)
506                         configureAsAbstractKotlinCompileTool(kspTask)
507 
508                         val useEmbeddable = project.findProperty("kotlin.native.useEmbeddableCompilerJar")
509                             ?.toString()?.toBoolean() ?: true
510                         val classpathCfg = if (useEmbeddable) {
511                             kspClasspathCfg
512                         } else {
513                             kspClasspathCfgNonEmbeddable
514                         }
515                         // KotlinNativeCompile computes -Xplugin=... from compilerPluginClasspath.
516                         if (kspExtension.blockOtherCompilerPlugins) {
517                             kspTask.compilerPluginClasspath = classpathCfg
518                         } else {
519                             kspTask.compilerPluginClasspath =
520                                 classpathCfg + kotlinCompileTask.compilerPluginClasspath!!
521                             kspTask.compilerPluginOptions.addPluginArgument(kotlinCompileTask.compilerPluginOptions)
522                         }
523                         kspTask.commonSources.from(kotlinCompileTask.commonSources)
524                         val kspOptions = kspTask.options.get().flatMap { listOf("-P", it.toArg()) }
525                         kspTask.compilerOptions.freeCompilerArgs.value(
526                             kspOptions + kotlinCompileTask.compilerOptions.freeCompilerArgs.get()
527                         )
528                         kspTask.compilerOptions.useK2.value(false)
529                         // Cannot use lambda; See below for details.
530                         // https://docs.gradle.org/7.2/userguide/validation_problems.html#implementation_unknown
531                         kspTask.doFirst(object : Action<Task> {
532                             override fun execute(t: Task) {
533                                 kspOutputDir.deleteRecursively()
534                             }
535                         })
536                     }
537                 }
538             }
539             // No else; The cases should be exhaustive
540         }
541         kspGeneratedSourceSet.kotlin.srcDir(project.files(kotlinOutputDir, javaOutputDir).builtBy(kspTaskProvider))
542         kotlinCompilation.source(kspGeneratedSourceSet)
543         kotlinCompileProvider.configure { kotlinCompile ->
544             when (kotlinCompile) {
545                 is AbstractKotlinCompile<*> -> kotlinCompile.libraries.from(project.files(classOutputDir))
546                 // is KotlinNativeCompile -> TODO: support binary generation?
547             }
548         }
549 
550         val processResourcesTaskName =
551             (kotlinCompilation as? KotlinCompilationWithResources)?.processResourcesTaskName ?: "processResources"
552         project.locateTask<ProcessResources>(processResourcesTaskName)?.let { provider ->
553             provider.configure { resourcesTask ->
554                 resourcesTask.from(project.files(resourceOutputDir).builtBy(kspTaskProvider))
555             }
556         }
557         if (kotlinCompilation is KotlinJvmAndroidCompilation) {
558             AndroidPluginIntegration.registerGeneratedSources(
559                 project = project,
560                 kotlinCompilation = kotlinCompilation,
561                 kspTaskProvider = kspTaskProvider as TaskProvider<KspTaskJvm>,
562                 javaOutputDir = javaOutputDir,
563                 kotlinOutputDir = kotlinOutputDir,
564                 classOutputDir = classOutputDir,
565                 resourcesOutputDir = project.files(resourceOutputDir)
566             )
567         }
568 
569         return project.provider { emptyList() }
570     }
571 
572     override fun getCompilerPluginId() = KSP_PLUGIN_ID
573     override fun getPluginArtifact(): SubpluginArtifact =
574         SubpluginArtifact(
575             groupId = "com.google.devtools.ksp",
576             artifactId = KSP_COMPILER_PLUGIN_ID,
577             version = KSP_VERSION
578         )
579 
580     override fun getPluginArtifactForNative(): SubpluginArtifact? =
581         SubpluginArtifact(
582             groupId = "com.google.devtools.ksp",
583             artifactId = KSP_COMPILER_PLUGIN_ID_NON_EMBEDDABLE,
584             version = KSP_VERSION
585         )
586 }
587 
588 // Copied from kotlin-gradle-plugin, because they are internal.
locateTasknull589 internal inline fun <reified T : Task> Project.locateTask(name: String): TaskProvider<T>? =
590     try {
591         tasks.withType(T::class.java).named(name)
592     } catch (e: UnknownTaskException) {
593         null
594     }
595 
596 // Copied from kotlin-gradle-plugin, because they are internal.
findJavaTaskForKotlinCompilationnull597 internal fun findJavaTaskForKotlinCompilation(compilation: KotlinCompilation<*>): TaskProvider<out JavaCompile>? =
598     when (compilation) {
599         is KotlinJvmAndroidCompilation -> compilation.compileJavaTaskProvider
600         is KotlinWithJavaCompilation<*, *> -> compilation.compileJavaTaskProvider
601         is KotlinJvmCompilation -> compilation.compileJavaTaskProvider // may be null for Kotlin-only JVM target in MPP
602         else -> null
603     }
604 
605 internal val artifactType = Attribute.of("artifactType", String::class.java)
606 
maybeRegisterTransformnull607 internal fun maybeRegisterTransform(project: Project) {
608     // Use the same flag with KAPT, so as to share the same transformation in case KAPT and KSP are both enabled.
609     if (!project.extensions.extraProperties.has("KaptStructureTransformAdded")) {
610         val transformActionClass =
611             if (GradleVersion.current() >= GradleVersion.version("5.4"))
612                 StructureTransformAction::class.java
613             else
614 
615                 StructureTransformLegacyAction::class.java
616         project.dependencies.registerTransform(transformActionClass) { transformSpec ->
617             transformSpec.from.attribute(artifactType, "jar")
618             transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE)
619         }
620 
621         project.dependencies.registerTransform(transformActionClass) { transformSpec ->
622             transformSpec.from.attribute(artifactType, "directory")
623             transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE)
624         }
625 
626         project.extensions.extraProperties["KaptStructureTransformAdded"] = true
627     }
628 }
629 
getClassStructureFilesnull630 internal fun getClassStructureFiles(
631     project: Project,
632     libraries: ConfigurableFileCollection,
633 ): FileCollection {
634     maybeRegisterTransform(project)
635 
636     val classStructureIfIncremental = project.configurations.detachedConfiguration(
637         project.dependencies.create(project.files(project.provider { libraries }))
638     )
639 
640     return classStructureIfIncremental.incoming.artifactView { viewConfig ->
641         viewConfig.attributes.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE)
642     }.files
643 }
644 
645 // Reuse Kapt's infrastructure to compute affected names in classpath.
646 // This is adapted from KaptTask.findClasspathChanges.
findClasspathChangesnull647 internal fun findClasspathChanges(
648     changes: ChangedFiles,
649     cacheDir: File,
650     allDataFiles: Set<File>,
651     libs: List<File>,
652     processorCP: List<File>,
653 ): KaptClasspathChanges {
654     cacheDir.mkdirs()
655 
656     val changedFiles = (changes as? ChangedFiles.Known)?.let { it.modified + it.removed }?.toSet() ?: allDataFiles
657 
658     val loadedPrevious = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(cacheDir)
659     val previousAndCurrentDataFiles = lazy { loadedPrevious.getAllDataFiles() + allDataFiles }
660     val allChangesRecognized = changedFiles.all {
661         val extension = it.extension
662         if (extension.isEmpty() || extension == "kt" || extension == "java" || extension == "jar" ||
663             extension == "class"
664         ) {
665             return@all true
666         }
667         // if not a directory, Java source file, jar, or class, it has to be a structure file, in order to understand changes
668         it in previousAndCurrentDataFiles.value
669     }
670     val previousSnapshot = if (allChangesRecognized) {
671         loadedPrevious
672     } else {
673         ClasspathSnapshot.ClasspathSnapshotFactory.getEmptySnapshot()
674     }
675 
676     val currentSnapshot =
677         ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent(
678             cacheDir,
679             libs,
680             processorCP,
681             allDataFiles
682         )
683 
684     val classpathChanges = currentSnapshot.diff(previousSnapshot, changedFiles)
685     if (classpathChanges is KaptClasspathChanges.Unknown || changes is ChangedFiles.Unknown) {
686         cacheDir.deleteRecursively()
687         cacheDir.mkdirs()
688     }
689     currentSnapshot.writeToCache()
690 
691     return classpathChanges
692 }
693 
hasNonSourceChangenull694 internal fun ChangedFiles.hasNonSourceChange(): Boolean {
695     if (this !is ChangedFiles.Known)
696         return true
697 
698     return !(this.modified + this.removed).all {
699         it.isKotlinFile(listOf("kt")) || it.isJavaFile()
700     }
701 }
702 
toSubpluginOptionsnull703 fun KaptClasspathChanges.toSubpluginOptions(): List<SubpluginOption> {
704     return if (this is KaptClasspathChanges.Known) {
705         this.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty {
706             listOf(SubpluginOption("changedClasses", joinToString(":")))
707         } ?: emptyList()
708     } else {
709         emptyList()
710     }
711 }
712 
ChangedFilesnull713 fun ChangedFiles.toSubpluginOptions(): List<SubpluginOption> {
714     return if (this is ChangedFiles.Known) {
715         val options = mutableListOf<SubpluginOption>()
716         this.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty {
717             options += SubpluginOption("knownModified", map { it.path }.joinToString(File.pathSeparator))
718         }
719         this.removed.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty {
720             options += SubpluginOption("knownRemoved", map { it.path }.joinToString(File.pathSeparator))
721         }
722         options
723     } else {
724         emptyList()
725     }
726 }
727 
728 // Return a closure that captures required arguments only.
createIncrementalChangesTransformernull729 internal fun createIncrementalChangesTransformer(
730     isKspIncremental: Boolean,
731     isIntermoduleIncremental: Boolean,
732     cacheDir: File,
733     classpathStructure: Provider<FileCollection>,
734     libraries: Provider<FileCollection>,
735     processorCP: Provider<FileCollection>,
736 ): (ChangedFiles) -> List<SubpluginOption> = { changedFiles ->
737     val options = mutableListOf<SubpluginOption>()
738     if (isKspIncremental) {
739         if (isIntermoduleIncremental) {
740             // findClasspathChanges may clear caches, if there are
741             // 1. unknown changes, or
742             // 2. changes in annotation processors.
743             val classpathChanges = findClasspathChanges(
744                 changedFiles,
745                 cacheDir,
746                 classpathStructure.get().files,
747                 libraries.get().files.toList(),
748                 processorCP.get().files.toList()
749             )
750             options += classpathChanges.toSubpluginOptions()
751         } else {
752             if (changedFiles.hasNonSourceChange()) {
753                 cacheDir.deleteRecursively()
754             }
755         }
756     } else {
757         cacheDir.deleteRecursively()
758     }
759     options += changedFiles.toSubpluginOptions()
760 
761     options
762 }
763