xref: /aosp_15_r20/external/ksp/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinFactories.kt (revision af87fb4bb8e3042070d2a054e912924f599b22b7)
1 /*
<lambda>null2  * Copyright 2022 Google LLC
3  * Copyright 2010-2022 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 @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
19 
20 package com.google.devtools.ksp.gradle
21 
22 import org.gradle.api.Project
23 import org.gradle.api.Task
24 import org.gradle.api.file.FileCollection
25 import org.gradle.api.model.ObjectFactory
26 import org.gradle.api.provider.ListProperty
27 import org.gradle.api.provider.Property
28 import org.gradle.api.provider.ProviderFactory
29 import org.gradle.api.tasks.CacheableTask
30 import org.gradle.api.tasks.IgnoreEmptyDirectories
31 import org.gradle.api.tasks.InputFiles
32 import org.gradle.api.tasks.Internal
33 import org.gradle.api.tasks.Nested
34 import org.gradle.api.tasks.OutputDirectory
35 import org.gradle.api.tasks.PathSensitive
36 import org.gradle.api.tasks.PathSensitivity
37 import org.gradle.api.tasks.SkipWhenEmpty
38 import org.gradle.api.tasks.TaskProvider
39 import org.gradle.process.CommandLineArgumentProvider
40 import org.gradle.process.ExecOperations
41 import org.gradle.work.InputChanges
42 import org.gradle.workers.WorkerExecutor
43 import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
44 import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
45 import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
46 import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments
47 import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions
48 import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompilerOptionsDefault
49 import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsDefault
50 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonCompilerOptionsDefault
51 import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
52 import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
53 import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost
54 import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
55 import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinNativeCompilationData
56 import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
57 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
58 import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon
59 import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
60 import org.jetbrains.kotlin.gradle.tasks.TaskOutputsBackup
61 import org.jetbrains.kotlin.gradle.tasks.configuration.BaseKotlin2JsCompileConfig
62 import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileCommonConfig
63 import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileConfig
64 import org.jetbrains.kotlin.incremental.ChangedFiles
65 import java.io.File
66 import java.nio.file.Paths
67 import javax.inject.Inject
68 
69 /**
70  * TODO: Replace with KGP's Kotlin*Factory after:
71  * https://youtrack.jetbrains.com/issue/KT-54986/KGP-API-to-toggle-incremental-compilation
72  * https://youtrack.jetbrains.com/issue/KT-55031/KGP-API-to-create-compilation-tasks-of-JS-Metadata-and-Native
73  */
74 class KotlinFactories {
75     companion object {
76         fun registerKotlinJvmCompileTask(
77             project: Project,
78             taskName: String,
79             kotlinCompilation: KotlinCompilationData<*>,
80         ): TaskProvider<out KspTaskJvm> {
81             return project.tasks.register(taskName, KspTaskJvm::class.java).also { kspTaskProvider ->
82                 KotlinCompileConfig(kotlinCompilation)
83                     .execute(kspTaskProvider as TaskProvider<KotlinCompile>)
84 
85                 // useClasspathSnapshot isn't configurable per task.
86                 // Workaround: enable the other path and ignore irrelevant changes
87                 // See [KotlinCompileConfig] in for details.
88                 // FIXME: make it configurable in upstream or support useClasspathSnapshot == true, if possible.
89                 kspTaskProvider.configure {
90                     if (it.classpathSnapshotProperties.useClasspathSnapshot.get()) {
91                         it.classpathSnapshotProperties.classpath.from(project.provider { it.libraries })
92                     }
93                 }
94             }
95         }
96 
97         fun registerKotlinJSCompileTask(
98             project: Project,
99             taskName: String,
100             kotlinCompilation: KotlinCompilationData<*>,
101         ): TaskProvider<out KspTaskJS> {
102             return project.tasks.register(taskName, KspTaskJS::class.java).also { kspTaskProvider ->
103                 BaseKotlin2JsCompileConfig<Kotlin2JsCompile>(kotlinCompilation)
104                     .execute(kspTaskProvider as TaskProvider<Kotlin2JsCompile>)
105                 kspTaskProvider.configure {
106                     it.incrementalJsKlib = false
107                 }
108             }
109         }
110 
111         fun registerKotlinMetadataCompileTask(
112             project: Project,
113             taskName: String,
114             kotlinCompilation: KotlinCompilationData<*>,
115         ): TaskProvider<out KspTaskMetadata> {
116             return project.tasks.register(taskName, KspTaskMetadata::class.java).also { kspTaskProvider ->
117                 KotlinCompileCommonConfig(kotlinCompilation)
118                     .execute(kspTaskProvider as TaskProvider<KotlinCompileCommon>)
119             }
120         }
121 
122         fun registerKotlinNativeCompileTask(
123             project: Project,
124             taskName: String,
125             kotlinCompilation: KotlinCompilation<*>
126         ): TaskProvider<out KspTaskNative> {
127             return project.tasks.register(
128                 taskName,
129                 KspTaskNative::class.java,
130                 kotlinCompilation as KotlinNativeCompilationData<*>
131             ).apply {
132                 configure { kspTask ->
133                     kspTask.onlyIf {
134                         kspTask.konanTarget.enabledOnCurrentHost
135                     }
136                 }
137             }
138         }
139     }
140 }
141 
142 interface KspTask : Task {
143     @get:Internal
144     val options: ListProperty<SubpluginOption>
145 
146     @get:Nested
147     val commandLineArgumentProviders: ListProperty<CommandLineArgumentProvider>
148 
149     @get:Internal
150     val incrementalChangesTransformers: ListProperty<(ChangedFiles) -> List<SubpluginOption>>
151 }
152 
153 @CacheableTask
154 abstract class KspTaskJvm @Inject constructor(
155     workerExecutor: WorkerExecutor,
156     objectFactory: ObjectFactory
157 ) : KotlinCompile(
158         objectFactory.newInstance(KotlinJvmCompilerOptionsDefault::class.java),
159         workerExecutor,
160         objectFactory
161     ),
162     KspTask {
163     @get:OutputDirectory
164     abstract val destination: Property<File>
165 
166     // Override incrementalProps to exclude irrelevant changes
167     override val incrementalProps: List<FileCollection>
168         get() = listOf(
169             sources,
170             javaSources,
171             commonSourceSet,
172             classpathSnapshotProperties.classpath,
173         )
174 
175     // Overrding an internal function is hacky.
176     // TODO: Ask upstream to open it.
177     @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE")
callCompilerAsync$kotlin_gradle_plugin_commonnull178     fun `callCompilerAsync$kotlin_gradle_plugin_common`(
179         args: K2JVMCompilerArguments,
180         kotlinSources: Set<File>,
181         inputChanges: InputChanges,
182         taskOutputsBackup: TaskOutputsBackup?
183     ) {
184         val changedFiles = getChangedFiles(inputChanges, incrementalProps)
185         val extraOptions = incrementalChangesTransformers.get().flatMap {
186             it(changedFiles)
187         }
188         args.addPluginOptions(extraOptions)
189         super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup)
190     }
191 
skipConditionnull192     override fun skipCondition(): Boolean = sources.isEmpty && javaSources.isEmpty
193 
194     @get:InputFiles
195     @get:SkipWhenEmpty
196     @get:IgnoreEmptyDirectories
197     @get:PathSensitive(PathSensitivity.RELATIVE)
198     override val javaSources: FileCollection = super.javaSources.filter {
199         !destination.get().isParentOf(it)
200     }
201 }
202 
203 @CacheableTask
204 abstract class KspTaskJS @Inject constructor(
205     objectFactory: ObjectFactory,
206     workerExecutor: WorkerExecutor
207 ) : Kotlin2JsCompile(
208         objectFactory.newInstance(KotlinJsCompilerOptionsDefault::class.java),
209         objectFactory,
210         workerExecutor
211     ),
212     KspTask {
213 
214     // Overrding an internal function is hacky.
215     // TODO: Ask upstream to open it.
216     @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE")
callCompilerAsync$kotlin_gradle_plugin_commonnull217     fun `callCompilerAsync$kotlin_gradle_plugin_common`(
218         args: K2JSCompilerArguments,
219         kotlinSources: Set<File>,
220         inputChanges: InputChanges,
221         taskOutputsBackup: TaskOutputsBackup?
222     ) {
223         val changedFiles = getChangedFiles(inputChanges, incrementalProps)
224         val extraOptions = incrementalChangesTransformers.get().flatMap {
225             it(changedFiles)
226         }
227         args.addPluginOptions(extraOptions)
228         super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup)
229     }
230 }
231 
232 @CacheableTask
233 abstract class KspTaskMetadata @Inject constructor(
234     workerExecutor: WorkerExecutor,
235     objectFactory: ObjectFactory
236 ) : KotlinCompileCommon(
237         objectFactory.newInstance(KotlinMultiplatformCommonCompilerOptionsDefault::class.java),
238         workerExecutor,
239         objectFactory
240     ),
241     KspTask {
242 
243     // Overrding an internal function is hacky.
244     // TODO: Ask upstream to open it.
245     @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE")
callCompilerAsync$kotlin_gradle_plugin_commonnull246     fun `callCompilerAsync$kotlin_gradle_plugin_common`(
247         args: K2MetadataCompilerArguments,
248         kotlinSources: Set<File>,
249         inputChanges: InputChanges,
250         taskOutputsBackup: TaskOutputsBackup?
251     ) {
252         val changedFiles = getChangedFiles(inputChanges, incrementalProps)
253         val extraOptions = incrementalChangesTransformers.get().flatMap {
254             it(changedFiles)
255         }
256         args.addPluginOptions(extraOptions)
257         super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup)
258     }
259 }
260 
261 @CacheableTask
262 abstract class KspTaskNative @Inject internal constructor(
263     compilation: KotlinNativeCompilationData<*>,
264     objectFactory: ObjectFactory,
265     providerFactory: ProviderFactory,
266     execOperations: ExecOperations
267 ) : KotlinNativeCompile(compilation, objectFactory, providerFactory, execOperations), KspTask {
268 
269     override val compilerOptions: KotlinCommonCompilerOptions =
270         objectFactory.newInstance(KotlinMultiplatformCommonCompilerOptionsDefault::class.java)
271 }
272 
toArgnull273 internal fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value"
274 
275 internal fun CommonCompilerArguments.addPluginOptions(options: List<SubpluginOption>) {
276     pluginOptions = (options.map { it.toArg() } + pluginOptions!!).toTypedArray()
277 }
278 
isParentOfnull279 internal fun File.isParentOf(childCandidate: File): Boolean {
280     val parentPath = Paths.get(this.absolutePath).normalize()
281     val childCandidatePath = Paths.get(childCandidate.absolutePath).normalize()
282 
283     return childCandidatePath.startsWith(parentPath)
284 }
285