diff options
author | Ting-Yuan Huang <laszio@google.com> | 2022-11-27 23:02:41 -0800 |
---|---|---|
committer | laszio <ting-yuan@users.noreply.github.com> | 2022-11-29 11:01:29 -0800 |
commit | caa3e0843dd169dcc3486d1ac28d16489324d564 (patch) | |
tree | 87eb32b93a1ba148bf19e15adbc91030395d1a93 | |
parent | cb17cba0427e6fc8907dce808eb290afaf18c762 (diff) | |
download | ksp-caa3e0843dd169dcc3486d1ac28d16489324d564.tar.gz |
Refactoring: Move task creation to KotlinFactories
so as to prepare for migrating to KGP API
-rw-r--r-- | gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinFactories.kt | 447 | ||||
-rw-r--r-- | gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt | 505 |
2 files changed, 530 insertions, 422 deletions
diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinFactories.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinFactories.kt new file mode 100644 index 00000000..ada0e587 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinFactories.kt @@ -0,0 +1,447 @@ +/* + * Copyright 2022 Google LLC + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package com.google.devtools.ksp.gradle + +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.attributes.Attribute +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.IgnoreEmptyDirectories +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.LocalState +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.api.tasks.TaskProvider +import org.gradle.process.CommandLineArgumentProvider +import org.gradle.process.ExecOperations +import org.gradle.util.GradleVersion +import org.gradle.work.Incremental +import org.gradle.work.InputChanges +import org.gradle.workers.WorkerExecutor +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompilerOptionsDefault +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsDefault +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonCompilerOptionsDefault +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.CLASS_STRUCTURE_ARTIFACT_TYPE +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.ClasspathSnapshot +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptClasspathChanges +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.StructureTransformAction +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.StructureTransformLegacyAction +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationInfo +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption +import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile +import org.jetbrains.kotlin.gradle.tasks.TaskOutputsBackup +import org.jetbrains.kotlin.gradle.tasks.configuration.BaseKotlin2JsCompileConfig +import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileCommonConfig +import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileConfig +import org.jetbrains.kotlin.incremental.ChangedFiles +import org.jetbrains.kotlin.incremental.isJavaFile +import org.jetbrains.kotlin.incremental.isKotlinFile +import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty +import java.io.File +import java.nio.file.Paths +import javax.inject.Inject + +/** + * TODO: Replace with KGP's Kotlin*Factory after: + * https://youtrack.jetbrains.com/issue/KT-54986/KGP-API-to-toggle-incremental-compilation + * https://youtrack.jetbrains.com/issue/KT-55031/KGP-API-to-create-compilation-tasks-of-JS-Metadata-and-Native + */ +class KotlinFactories { + companion object { + fun registerKotlinJvmCompileTask( + project: Project, + taskName: String, + kotlinCompilation: KotlinCompilation<*>, + ): TaskProvider<out KspTaskJvm> { + return project.tasks.register(taskName, KspTaskJvm::class.java).also { kspTaskProvider -> + KotlinCompileConfig(KotlinCompilationInfo(kotlinCompilation)) + .execute(kspTaskProvider as TaskProvider<KotlinCompile>) + } + } + + fun registerKotlinJSCompileTask( + project: Project, + taskName: String, + kotlinCompilation: KotlinCompilation<*>, + ): TaskProvider<out KspTaskJS> { + return project.tasks.register(taskName, KspTaskJS::class.java).also { kspTaskProvider -> + BaseKotlin2JsCompileConfig<Kotlin2JsCompile>(KotlinCompilationInfo(kotlinCompilation)) + .execute(kspTaskProvider as TaskProvider<Kotlin2JsCompile>) + } + } + + fun registerKotlinMetadataCompileTask( + project: Project, + taskName: String, + kotlinCompilation: KotlinCompilation<*>, + ): TaskProvider<out KspTaskMetadata> { + return project.tasks.register(taskName, KspTaskMetadata::class.java).also { kspTaskProvider -> + KotlinCompileCommonConfig(KotlinCompilationInfo(kotlinCompilation)) + .execute(kspTaskProvider as TaskProvider<KotlinCompileCommon>) + } + } + + fun registerKotlinNativeCompileTask( + project: Project, + taskName: String, + kotlinCompileTask: KotlinNativeCompile + ): TaskProvider<out KspTaskNative> { + return project.tasks.register(taskName, KspTaskNative::class.java, kotlinCompileTask.compilation).apply { + configure { kspTask -> + kspTask.onlyIf { + kspTask.konanTarget.enabledOnCurrentHost + } + } + } + } + } +} + +private val artifactType = Attribute.of("artifactType", String::class.java) + +interface KspTask : Task { + @get:Internal + val options: ListProperty<SubpluginOption> + + @get:Nested + val commandLineArgumentProviders: ListProperty<CommandLineArgumentProvider> + + @get:OutputDirectory + var destination: File + + @get:Classpath + val processorClasspath: ConfigurableFileCollection + + /** + * Output directory that contains caches necessary to support incremental annotation processing. + */ + @get:LocalState + val kspCacheDir: DirectoryProperty + + @get:Input + var isKspIncremental: Boolean +} + +@CacheableTask +abstract class KspTaskJvm @Inject constructor( + workerExecutor: WorkerExecutor, + objectFactory: ObjectFactory +) : KotlinCompile( + objectFactory.newInstance(KotlinJvmCompilerOptionsDefault::class.java), + workerExecutor, + objectFactory +), + KspTask { + @get:PathSensitive(PathSensitivity.NONE) + @get:Optional + @get:InputFiles + @get:Incremental + abstract val classpathStructure: ConfigurableFileCollection + + @get:Input + var isIntermoduleIncremental: Boolean = false + + fun configureClasspathSnapshot() { + isIntermoduleIncremental = + (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && + isKspIncremental + if (isIntermoduleIncremental) { + val classStructureIfIncremental = project.configurations.detachedConfiguration( + project.dependencies.create(project.files(project.provider { libraries })) + ) + maybeRegisterTransform(project) + + classpathStructure.from( + classStructureIfIncremental.incoming.artifactView { viewConfig -> + viewConfig.attributes.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + }.files + ).disallowChanges() + } + } + + private fun maybeRegisterTransform(project: Project) { + // Use the same flag with KAPT, so as to share the same transformation in case KAPT and KSP are both enabled. + if (!project.extensions.extraProperties.has("KaptStructureTransformAdded")) { + val transformActionClass = + if (GradleVersion.current() >= GradleVersion.version("5.4")) + StructureTransformAction::class.java + else + + StructureTransformLegacyAction::class.java + project.dependencies.registerTransform(transformActionClass) { transformSpec -> + transformSpec.from.attribute(artifactType, "jar") + transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + } + + project.dependencies.registerTransform(transformActionClass) { transformSpec -> + transformSpec.from.attribute(artifactType, "directory") + transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + } + + project.extensions.extraProperties["KaptStructureTransformAdded"] = true + } + } + + // Reuse Kapt's infrastructure to compute affected names in classpath. + // This is adapted from KaptTask.findClasspathChanges. + private fun findClasspathChanges( + changes: ChangedFiles, + ): KaptClasspathChanges { + val cacheDir = kspCacheDir.asFile.get() + cacheDir.mkdirs() + + val allDataFiles = classpathStructure.files + val changedFiles = (changes as? ChangedFiles.Known)?.let { it.modified + it.removed }?.toSet() ?: allDataFiles + + val loadedPrevious = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(cacheDir) + val previousAndCurrentDataFiles = lazy { loadedPrevious.getAllDataFiles() + allDataFiles } + val allChangesRecognized = changedFiles.all { + val extension = it.extension + if (extension.isEmpty() || extension == "kt" || extension == "java" || extension == "jar" || + extension == "class" + ) { + return@all true + } + // if not a directory, Java source file, jar, or class, it has to be a structure file, in order to understand changes + it in previousAndCurrentDataFiles.value + } + val previousSnapshot = if (allChangesRecognized) { + loadedPrevious + } else { + ClasspathSnapshot.ClasspathSnapshotFactory.getEmptySnapshot() + } + + val currentSnapshot = + ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent( + cacheDir, + libraries.files.toList(), + processorClasspath.files.toList(), + allDataFiles + ) + + val classpathChanges = currentSnapshot.diff(previousSnapshot, changedFiles) + if (classpathChanges is KaptClasspathChanges.Unknown || changes is ChangedFiles.Unknown) { + clearIncCache() + cacheDir.mkdirs() + } + currentSnapshot.writeToCache() + + return classpathChanges + } + + init { + // Mute a warning from ScriptingGradleSubplugin, which tries to get `sourceSetName` before this task is + // configured. + sourceSetName.set("main") + } + + // Overrding an internal function is hacky. + // TODO: Ask upstream to open it. + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") + fun `callCompilerAsync$kotlin_gradle_plugin_common`( + args: K2JVMCompilerArguments, + kotlinSources: Set<File>, + inputChanges: InputChanges, + taskOutputsBackup: TaskOutputsBackup? + ) { + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (isKspIncremental) { + if (isIntermoduleIncremental) { + // findClasspathChanges may clear caches, if there are + // 1. unknown changes, or + // 2. changes in annotation processors. + val classpathChanges = findClasspathChanges(changedFiles) + args.addChangedClasses(classpathChanges) + } else { + if (changedFiles.hasNonSourceChange()) { + clearIncCache() + } + } + } else { + clearIncCache() + } + args.addChangedFiles(changedFiles) + args.allowNoSourceFiles = true + super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) + } + + override fun skipCondition(): Boolean = sources.isEmpty && javaSources.isEmpty + + override val incrementalProps: List<FileCollection> + get() = listOf( + sources, + javaSources, + commonSourceSet, + classpathSnapshotProperties.classpath, + classpathSnapshotProperties.classpathSnapshot + ) + + @get:InputFiles + @get:SkipWhenEmpty + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + override val javaSources: FileCollection = super.javaSources.filter { + !destination.isParentOf(it) + } +} + +@CacheableTask +abstract class KspTaskJS @Inject constructor( + objectFactory: ObjectFactory, + workerExecutor: WorkerExecutor +) : Kotlin2JsCompile( + objectFactory.newInstance(KotlinJsCompilerOptionsDefault::class.java), + objectFactory, + workerExecutor +), + KspTask { + + // Overrding an internal function is hacky. + // TODO: Ask upstream to open it. + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") + fun `callCompilerAsync$kotlin_gradle_plugin_common`( + args: K2JSCompilerArguments, + kotlinSources: Set<File>, + inputChanges: InputChanges, + taskOutputsBackup: TaskOutputsBackup? + ) { + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (!isKspIncremental || changedFiles.hasNonSourceChange()) { + clearIncCache() + } else { + args.addChangedFiles(changedFiles) + } + super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) + } +} + +@CacheableTask +abstract class KspTaskMetadata @Inject constructor( + workerExecutor: WorkerExecutor, + objectFactory: ObjectFactory +) : KotlinCompileCommon( + objectFactory.newInstance(KotlinMultiplatformCommonCompilerOptionsDefault::class.java), + workerExecutor, + objectFactory +), + KspTask { + + // Overrding an internal function is hacky. + // TODO: Ask upstream to open it. + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") + fun `callCompilerAsync$kotlin_gradle_plugin_common`( + args: K2MetadataCompilerArguments, + kotlinSources: Set<File>, + inputChanges: InputChanges, + taskOutputsBackup: TaskOutputsBackup? + ) { + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (!isKspIncremental || changedFiles.hasNonSourceChange()) { + clearIncCache() + } else { + args.addChangedFiles(changedFiles) + } + args.expectActualLinker = true + super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) + } +} + +@CacheableTask +abstract class KspTaskNative @Inject internal constructor( + compilation: KotlinCompilationInfo, + objectFactory: ObjectFactory, + providerFactory: ProviderFactory, + execOperations: ExecOperations +) : KotlinNativeCompile(compilation, objectFactory, providerFactory, execOperations), KspTask { + + override val compilerOptions: KotlinCommonCompilerOptions = + objectFactory.newInstance(KotlinMultiplatformCommonCompilerOptionsDefault::class.java) +} + +// This forces rebuild. +private fun KspTask.clearIncCache() { + kspCacheDir.get().asFile.deleteRecursively() +} + +private fun ChangedFiles.hasNonSourceChange(): Boolean { + if (this !is ChangedFiles.Known) + return true + + return !(this.modified + this.removed).all { + it.isKotlinFile(listOf("kt")) || it.isJavaFile() + } +} + +fun CommonCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { + if (changed is KaptClasspathChanges.Known) { + changed.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty { + addPluginOptions(listOf(SubpluginOption("changedClasses", joinToString(":")))) + } + } +} + +fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value" + +fun CommonCompilerArguments.addPluginOptions(options: List<SubpluginOption>) { + pluginOptions = (options.map { it.toArg() } + pluginOptions!!).toTypedArray() +} + +fun CommonCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { + if (changedFiles is ChangedFiles.Known) { + val options = mutableListOf<SubpluginOption>() + changedFiles.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { + options += SubpluginOption("knownModified", map { it.path }.joinToString(File.pathSeparator)) + } + changedFiles.removed.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { + options += SubpluginOption("knownRemoved", map { it.path }.joinToString(File.pathSeparator)) + } + options.ifNotEmpty { addPluginOptions(this) } + } +} + +internal fun File.isParentOf(childCandidate: File): Boolean { + val parentPath = Paths.get(this.absolutePath).normalize() + val childCandidatePath = Paths.get(childCandidate.absolutePath).normalize() + + return childCandidatePath.startsWith(parentPath) +} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt index f8cf179d..68258372 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt @@ -15,8 +15,6 @@ * limitations under the License. */ -@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") - package com.google.devtools.ksp.gradle import com.google.devtools.ksp.gradle.model.builder.KspModelBuilder @@ -24,43 +22,35 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.UnknownTaskException import org.gradle.api.artifacts.Configuration -import org.gradle.api.attributes.Attribute -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.FileCollection -import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.tasks.* -import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.compile.JavaCompile import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.process.CommandLineArgumentProvider -import org.gradle.process.ExecOperations import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry -import org.gradle.util.GradleVersion -import org.gradle.work.Incremental -import org.gradle.work.InputChanges -import org.gradle.workers.WorkerExecutor -import org.jetbrains.kotlin.cli.common.arguments.* import org.jetbrains.kotlin.config.ApiVersion -import org.jetbrains.kotlin.gradle.dsl.* -import org.jetbrains.kotlin.gradle.internal.kapt.incremental.* -import org.jetbrains.kotlin.gradle.plugin.* -import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.targets.js.ir.* -import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.plugin.CompilerPluginConfig +import org.jetbrains.kotlin.gradle.plugin.FilesSubpluginOption +import org.jetbrains.kotlin.gradle.plugin.InternalSubpluginOption +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationWithResources +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin +import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption +import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool +import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.jetbrains.kotlin.gradle.tasks.configuration.BaseKotlin2JsCompileConfig -import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileCommonConfig -import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileConfig -import org.jetbrains.kotlin.incremental.ChangedFiles -import org.jetbrains.kotlin.incremental.isJavaFile -import org.jetbrains.kotlin.incremental.isKotlinFile -import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty +import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile import java.io.File -import java.nio.file.Paths import java.util.concurrent.Callable import javax.inject.Inject @@ -311,104 +301,84 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool ) } - // Create KSP tasks and configure later. - val kspTaskProvider = when (kotlinCompileTask) { - is KotlinCompile -> - project.tasks.register(kspTaskName, KspTaskJvm::class.java) - is Kotlin2JsCompile -> - project.tasks.register(kspTaskName, KspTaskJS::class.java) - is KotlinCompileCommon -> - project.tasks.register(kspTaskName, KspTaskMetadata::class.java) - is KotlinNativeCompile -> - project.tasks.register(kspTaskName, KspTaskNative::class.java, kotlinCompileTask.compilation) - else -> return project.provider { emptyList() } - } - val isIncremental = project.findProperty("ksp.incremental")?.toString()?.toBoolean() ?: true - // Configure KSP tasks - when (kotlinCompileTask) { + // Create and configure KSP tasks. + val kspTaskProvider = when (kotlinCompileTask) { is KotlinCompile -> { - kspTaskProvider.configure { kspTask -> - kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) - } - KotlinCompileConfig(KotlinCompilationInfo(kotlinCompilation)) - .execute(kspTaskProvider as TaskProvider<KotlinCompile>) - kspTaskProvider.configure { kspTask -> - kspTask as KspTaskJvm - maybeBlockOtherPlugins(kspTask as BaseKotlinCompile) - configureAsKspTask(kspTask as KspTask, isIncremental) - configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) - configurePluginOptions(kspTask) - kspTask.configureClasspathSnapshot() - kspTask.compilerOptions.noJdk.value(kotlinCompileTask.compilerOptions.noJdk) - kspTask.compilerOptions.useK2.value(false) - kspTask.compilerOptions.moduleName.convention(kotlinCompileTask.moduleName.map { "$it-ksp" }) - kspTask.moduleName.value(kotlinCompileTask.moduleName.get()) + KotlinFactories.registerKotlinJvmCompileTask(project, kspTaskName, kotlinCompilation).also { + it.configure { kspTask -> + maybeBlockOtherPlugins(kspTask as BaseKotlinCompile) + configureAsKspTask(kspTask, isIncremental) + configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) + configurePluginOptions(kspTask) + kspTask.configureClasspathSnapshot() + kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) + kspTask.compilerOptions.noJdk.value(kotlinCompileTask.compilerOptions.noJdk) + kspTask.compilerOptions.useK2.value(false) + kspTask.compilerOptions.moduleName.convention(kotlinCompileTask.moduleName.map { "$it-ksp" }) + kspTask.moduleName.value(kotlinCompileTask.moduleName.get()) + } + // Don't support binary generation for non-JVM platforms yet. + // FIXME: figure out how to add user generated libraries. + kotlinCompilation.output.classesDirs.from(classOutputDir) } - // Don't support binary generation for non-JVM platforms yet. - // FIXME: figure out how to add user generated libraries. - kotlinCompilation.output.classesDirs.from(classOutputDir) } is Kotlin2JsCompile -> { - kspTaskProvider.configure { kspTask -> - kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) - kspTask.compilerOptions.freeCompilerArgs.value(kotlinCompileTask.compilerOptions.freeCompilerArgs) - } - BaseKotlin2JsCompileConfig<Kotlin2JsCompile>(KotlinCompilationInfo(kotlinCompilation)) - .execute(kspTaskProvider as TaskProvider<Kotlin2JsCompile>) - kspTaskProvider.configure { kspTask -> - kspTask as KspTaskJS - maybeBlockOtherPlugins(kspTask as BaseKotlinCompile) - configureAsKspTask(kspTask as KspTask, isIncremental) - configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) - configurePluginOptions(kspTask) - kspTask.compilerOptions.useK2.value(false) - kspTask.compilerOptions.moduleName.convention(kotlinCompileTask.moduleName.map { "$it-ksp" }) - (kspTask as KspTaskJS).incrementalJsKlib = false + KotlinFactories.registerKotlinJSCompileTask(project, kspTaskName, kotlinCompilation).also { + it.configure { kspTask -> + maybeBlockOtherPlugins(kspTask as BaseKotlinCompile) + configureAsKspTask(kspTask, isIncremental) + configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) + configurePluginOptions(kspTask) + kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) + kspTask.compilerOptions.freeCompilerArgs.value(kotlinCompileTask.compilerOptions.freeCompilerArgs) + kspTask.compilerOptions.useK2.value(false) + kspTask.compilerOptions.moduleName.convention(kotlinCompileTask.moduleName.map { "$it-ksp" }) + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + kspTask.incrementalJsKlib = false + } } } is KotlinCompileCommon -> { - kspTaskProvider.configure { kspTask -> - kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) - } - KotlinCompileCommonConfig(KotlinCompilationInfo(kotlinCompilation)) - .execute(kspTaskProvider as TaskProvider<KotlinCompileCommon>) - kspTaskProvider.configure { kspTask -> - maybeBlockOtherPlugins(kspTask as BaseKotlinCompile) - configureAsKspTask(kspTask as KspTask, isIncremental) - configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) - configurePluginOptions(kspTask) - kspTask.compilerOptions.useK2.value(false) + KotlinFactories.registerKotlinMetadataCompileTask(project, kspTaskName, kotlinCompilation).also { + it.configure { kspTask -> + maybeBlockOtherPlugins(kspTask as BaseKotlinCompile) + configureAsKspTask(kspTask, isIncremental) + configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) + configurePluginOptions(kspTask) + kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) + kspTask.compilerOptions.useK2.value(false) + } } } is KotlinNativeCompile -> { - kspTaskProvider.configure { kspTask -> - kspTask as KspTaskNative - kspTask.onlyIf { - kspTask.konanTarget.enabledOnCurrentHost - } - configureAsKspTask(kspTask, false) - configureAsAbstractKotlinCompileTool(kspTask) - - // KotlinNativeCompile computes -Xplugin=... from compilerPluginClasspath. - if (kspExtension.blockOtherCompilerPlugins) { - kspTask.compilerPluginClasspath = kspClasspathCfg - } else { - kspTask.compilerPluginClasspath = - kspClasspathCfg + kotlinCompileTask.compilerPluginClasspath!! - kspTask.compilerPluginOptions.addPluginArgument(kotlinCompileTask.compilerPluginOptions) - } - kspTask.commonSources.from(kotlinCompileTask.commonSources) - val kspOptions = kspTask.options.get().flatMap { listOf("-P", it.toArg()) } - kspTask.compilerOptions.freeCompilerArgs.value( - kspOptions + kotlinCompileTask.compilerOptions.freeCompilerArgs.get() - ) - kspTask.doFirst { - kspOutputDir.deleteRecursively() + KotlinFactories.registerKotlinNativeCompileTask(project, kspTaskName, kotlinCompileTask).also { + it.configure { kspTask -> + configureAsKspTask(kspTask, false) + configureAsAbstractKotlinCompileTool(kspTask) + + // KotlinNativeCompile computes -Xplugin=... from compilerPluginClasspath. + if (kspExtension.blockOtherCompilerPlugins) { + kspTask.compilerPluginClasspath = kspClasspathCfg + } else { + kspTask.compilerPluginClasspath = + kspClasspathCfg + kotlinCompileTask.compilerPluginClasspath!! + kspTask.compilerPluginOptions.addPluginArgument(kotlinCompileTask.compilerPluginOptions) + } + kspTask.commonSources.from(kotlinCompileTask.commonSources) + val kspOptions = kspTask.options.get().flatMap { listOf("-P", it.toArg()) } + kspTask.compilerOptions.freeCompilerArgs.value( + kspOptions + kotlinCompileTask.compilerOptions.freeCompilerArgs.get() + ) + kspTask.doFirst { + kspOutputDir.deleteRecursively() + } } + } } + else -> return project.provider { emptyList() } } kotlinCompileProvider.configure { kotlinCompile -> @@ -461,8 +431,6 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool val apiArtifact = "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION" } -private val artifactType = Attribute.of("artifactType", String::class.java) - // Copied from kotlin-gradle-plugin, because they are internal. internal inline fun <reified T : Task> Project.locateTask(name: String): TaskProvider<T>? = try { @@ -479,310 +447,3 @@ internal fun findJavaTaskForKotlinCompilation(compilation: KotlinCompilation<*>) is KotlinJvmCompilation -> compilation.compileJavaTaskProvider // may be null for Kotlin-only JVM target in MPP else -> null } - -interface KspTask : Task { - @get:Internal - val options: ListProperty<SubpluginOption> - - @get:Nested - val commandLineArgumentProviders: ListProperty<CommandLineArgumentProvider> - - @get:OutputDirectory - var destination: File - - @get:Classpath - val processorClasspath: ConfigurableFileCollection - - /** - * Output directory that contains caches necessary to support incremental annotation processing. - */ - @get:LocalState - val kspCacheDir: DirectoryProperty - - @get:Input - var isKspIncremental: Boolean -} - -@CacheableTask -abstract class KspTaskJvm @Inject constructor( - workerExecutor: WorkerExecutor, - objectFactory: ObjectFactory -) : KotlinCompile( - objectFactory.newInstance(KotlinJvmCompilerOptionsDefault::class.java), - workerExecutor, - objectFactory - ), - KspTask { - @get:PathSensitive(PathSensitivity.NONE) - @get:Optional - @get:InputFiles - @get:Incremental - abstract val classpathStructure: ConfigurableFileCollection - - @get:Input - var isIntermoduleIncremental: Boolean = false - - fun configureClasspathSnapshot() { - isIntermoduleIncremental = - (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && - isKspIncremental - if (isIntermoduleIncremental) { - val classStructureIfIncremental = project.configurations.detachedConfiguration( - project.dependencies.create(project.files(project.provider { libraries })) - ) - maybeRegisterTransform(project) - - classpathStructure.from( - classStructureIfIncremental.incoming.artifactView { viewConfig -> - viewConfig.attributes.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) - }.files - ).disallowChanges() - } - } - - private fun maybeRegisterTransform(project: Project) { - // Use the same flag with KAPT, so as to share the same transformation in case KAPT and KSP are both enabled. - if (!project.extensions.extraProperties.has("KaptStructureTransformAdded")) { - val transformActionClass = - if (GradleVersion.current() >= GradleVersion.version("5.4")) - StructureTransformAction::class.java - else - - StructureTransformLegacyAction::class.java - project.dependencies.registerTransform(transformActionClass) { transformSpec -> - transformSpec.from.attribute(artifactType, "jar") - transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) - } - - project.dependencies.registerTransform(transformActionClass) { transformSpec -> - transformSpec.from.attribute(artifactType, "directory") - transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) - } - - project.extensions.extraProperties["KaptStructureTransformAdded"] = true - } - } - - // Reuse Kapt's infrastructure to compute affected names in classpath. - // This is adapted from KaptTask.findClasspathChanges. - private fun findClasspathChanges( - changes: ChangedFiles, - ): KaptClasspathChanges { - val cacheDir = kspCacheDir.asFile.get() - cacheDir.mkdirs() - - val allDataFiles = classpathStructure.files - val changedFiles = (changes as? ChangedFiles.Known)?.let { it.modified + it.removed }?.toSet() ?: allDataFiles - - val loadedPrevious = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(cacheDir) - val previousAndCurrentDataFiles = lazy { loadedPrevious.getAllDataFiles() + allDataFiles } - val allChangesRecognized = changedFiles.all { - val extension = it.extension - if (extension.isEmpty() || extension == "kt" || extension == "java" || extension == "jar" || - extension == "class" - ) { - return@all true - } - // if not a directory, Java source file, jar, or class, it has to be a structure file, in order to understand changes - it in previousAndCurrentDataFiles.value - } - val previousSnapshot = if (allChangesRecognized) { - loadedPrevious - } else { - ClasspathSnapshot.ClasspathSnapshotFactory.getEmptySnapshot() - } - - val currentSnapshot = - ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent( - cacheDir, - libraries.files.toList(), - processorClasspath.files.toList(), - allDataFiles - ) - - val classpathChanges = currentSnapshot.diff(previousSnapshot, changedFiles) - if (classpathChanges is KaptClasspathChanges.Unknown || changes is ChangedFiles.Unknown) { - clearIncCache() - cacheDir.mkdirs() - } - currentSnapshot.writeToCache() - - return classpathChanges - } - - init { - // Mute a warning from ScriptingGradleSubplugin, which tries to get `sourceSetName` before this task is - // configured. - sourceSetName.set("main") - } - - // Overrding an internal function is hacky. - // TODO: Ask upstream to open it. - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") - fun `callCompilerAsync$kotlin_gradle_plugin_common`( - args: K2JVMCompilerArguments, - kotlinSources: Set<File>, - inputChanges: InputChanges, - taskOutputsBackup: TaskOutputsBackup? - ) { - val changedFiles = getChangedFiles(inputChanges, incrementalProps) - if (isKspIncremental) { - if (isIntermoduleIncremental) { - // findClasspathChanges may clear caches, if there are - // 1. unknown changes, or - // 2. changes in annotation processors. - val classpathChanges = findClasspathChanges(changedFiles) - args.addChangedClasses(classpathChanges) - } else { - if (changedFiles.hasNonSourceChange()) { - clearIncCache() - } - } - } else { - clearIncCache() - } - args.addChangedFiles(changedFiles) - args.allowNoSourceFiles = true - super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) - } - - override fun skipCondition(): Boolean = sources.isEmpty && javaSources.isEmpty - - override val incrementalProps: List<FileCollection> - get() = listOf( - sources, - javaSources, - commonSourceSet, - classpathSnapshotProperties.classpath, - classpathSnapshotProperties.classpathSnapshot - ) - - @get:InputFiles - @get:SkipWhenEmpty - @get:IgnoreEmptyDirectories - @get:PathSensitive(PathSensitivity.RELATIVE) - override val javaSources: FileCollection = super.javaSources.filter { - !destination.isParentOf(it) - } -} - -@CacheableTask -abstract class KspTaskJS @Inject constructor( - objectFactory: ObjectFactory, - workerExecutor: WorkerExecutor -) : Kotlin2JsCompile( - objectFactory.newInstance(KotlinJsCompilerOptionsDefault::class.java), - objectFactory, - workerExecutor - ), - KspTask { - - // Overrding an internal function is hacky. - // TODO: Ask upstream to open it. - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") - fun `callCompilerAsync$kotlin_gradle_plugin_common`( - args: K2JSCompilerArguments, - kotlinSources: Set<File>, - inputChanges: InputChanges, - taskOutputsBackup: TaskOutputsBackup? - ) { - val changedFiles = getChangedFiles(inputChanges, incrementalProps) - if (!isKspIncremental || changedFiles.hasNonSourceChange()) { - clearIncCache() - } else { - args.addChangedFiles(changedFiles) - } - super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) - } -} - -@CacheableTask -abstract class KspTaskMetadata @Inject constructor( - workerExecutor: WorkerExecutor, - objectFactory: ObjectFactory -) : KotlinCompileCommon( - objectFactory.newInstance(KotlinMultiplatformCommonCompilerOptionsDefault::class.java), - workerExecutor, - objectFactory - ), - KspTask { - - // Overrding an internal function is hacky. - // TODO: Ask upstream to open it. - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") - fun `callCompilerAsync$kotlin_gradle_plugin_common`( - args: K2MetadataCompilerArguments, - kotlinSources: Set<File>, - inputChanges: InputChanges, - taskOutputsBackup: TaskOutputsBackup? - ) { - val changedFiles = getChangedFiles(inputChanges, incrementalProps) - if (!isKspIncremental || changedFiles.hasNonSourceChange()) { - clearIncCache() - } else { - args.addChangedFiles(changedFiles) - } - args.expectActualLinker = true - super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) - } -} - -@CacheableTask -abstract class KspTaskNative @Inject internal constructor( - compilation: KotlinCompilationInfo, - objectFactory: ObjectFactory, - providerFactory: ProviderFactory, - execOperations: ExecOperations -) : KotlinNativeCompile(compilation, objectFactory, providerFactory, execOperations), KspTask { - - override val compilerOptions: KotlinCommonCompilerOptions = - objectFactory.newInstance(KotlinMultiplatformCommonCompilerOptionsDefault::class.java) -} - -// This forces rebuild. -private fun KspTask.clearIncCache() { - kspCacheDir.get().asFile.deleteRecursively() -} - -private fun ChangedFiles.hasNonSourceChange(): Boolean { - if (this !is ChangedFiles.Known) - return true - - return !(this.modified + this.removed).all { - it.isKotlinFile(listOf("kt")) || it.isJavaFile() - } -} - -fun CommonCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { - if (changed is KaptClasspathChanges.Known) { - changed.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty { - addPluginOptions(listOf(SubpluginOption("changedClasses", joinToString(":")))) - } - } -} - -fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value" - -fun CommonCompilerArguments.addPluginOptions(options: List<SubpluginOption>) { - pluginOptions = (options.map { it.toArg() } + pluginOptions!!).toTypedArray() -} - -fun CommonCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { - if (changedFiles is ChangedFiles.Known) { - val options = mutableListOf<SubpluginOption>() - changedFiles.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { - options += SubpluginOption("knownModified", map { it.path }.joinToString(File.pathSeparator)) - } - changedFiles.removed.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { - options += SubpluginOption("knownRemoved", map { it.path }.joinToString(File.pathSeparator)) - } - options.ifNotEmpty { addPluginOptions(this) } - } -} - -internal fun File.isParentOf(childCandidate: File): Boolean { - val parentPath = Paths.get(this.absolutePath).normalize() - val childCandidatePath = Paths.get(childCandidate.absolutePath).normalize() - - return childCandidatePath.startsWith(parentPath) -} |