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