diff options
author | Ting-Yuan Huang <laszio@google.com> | 2022-11-28 10:34:01 -0800 |
---|---|---|
committer | laszio <ting-yuan@users.noreply.github.com> | 2022-11-29 11:01:29 -0800 |
commit | 1ed2d3b709adc53450eb1c09fffe33a50459cc88 (patch) | |
tree | f8ad2716ffc6946479828c97cc20be1920aeb399 | |
parent | caa3e0843dd169dcc3486d1ac28d16489324d564 (diff) | |
download | ksp-1ed2d3b709adc53450eb1c09fffe33a50459cc88.tar.gz |
Pass changes in incrementalChangesTransformer
-rw-r--r-- | gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinFactories.kt | 245 | ||||
-rw-r--r-- | gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt | 240 |
2 files changed, 256 insertions, 229 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 index ada0e587..6e9e4a7b 100644 --- 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 @@ -21,22 +21,16 @@ 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.Property 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 @@ -44,8 +38,6 @@ 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 @@ -56,11 +48,6 @@ 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 @@ -74,9 +61,6 @@ import org.jetbrains.kotlin.gradle.tasks.configuration.BaseKotlin2JsCompileConfi 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 @@ -107,6 +91,9 @@ class KotlinFactories { return project.tasks.register(taskName, KspTaskJS::class.java).also { kspTaskProvider -> BaseKotlin2JsCompileConfig<Kotlin2JsCompile>(KotlinCompilationInfo(kotlinCompilation)) .execute(kspTaskProvider as TaskProvider<Kotlin2JsCompile>) + kspTaskProvider.configure { + it.incrementalJsKlib = false + } } } @@ -137,8 +124,6 @@ class KotlinFactories { } } -private val artifactType = Attribute.of("artifactType", String::class.java) - interface KspTask : Task { @get:Internal val options: ListProperty<SubpluginOption> @@ -146,20 +131,8 @@ interface KspTask : Task { @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 + @get:Internal + val incrementalChangesTransformers: ListProperty<(ChangedFiles) -> List<SubpluginOption>> } @CacheableTask @@ -167,114 +140,20 @@ abstract class KspTaskJvm @Inject constructor( workerExecutor: WorkerExecutor, objectFactory: ObjectFactory ) : KotlinCompile( - objectFactory.newInstance(KotlinJvmCompilerOptionsDefault::class.java), - workerExecutor, - objectFactory -), + 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") } + @get:OutputDirectory + abstract val destination: Property<File> + // Overrding an internal function is hacky. // TODO: Ask upstream to open it. @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") @@ -285,43 +164,22 @@ abstract class KspTaskJvm @Inject constructor( 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() + val extraOptions = incrementalChangesTransformers.get().flatMap { + it(changedFiles) } - args.addChangedFiles(changedFiles) + args.addPluginOptions(extraOptions) 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) + !destination.get().isParentOf(it) } } @@ -330,10 +188,10 @@ abstract class KspTaskJS @Inject constructor( objectFactory: ObjectFactory, workerExecutor: WorkerExecutor ) : Kotlin2JsCompile( - objectFactory.newInstance(KotlinJsCompilerOptionsDefault::class.java), - objectFactory, - workerExecutor -), + objectFactory.newInstance(KotlinJsCompilerOptionsDefault::class.java), + objectFactory, + workerExecutor + ), KspTask { // Overrding an internal function is hacky. @@ -346,11 +204,10 @@ abstract class KspTaskJS @Inject constructor( taskOutputsBackup: TaskOutputsBackup? ) { val changedFiles = getChangedFiles(inputChanges, incrementalProps) - if (!isKspIncremental || changedFiles.hasNonSourceChange()) { - clearIncCache() - } else { - args.addChangedFiles(changedFiles) + val extraOptions = incrementalChangesTransformers.get().flatMap { + it(changedFiles) } + args.addPluginOptions(extraOptions) super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) } } @@ -360,10 +217,10 @@ abstract class KspTaskMetadata @Inject constructor( workerExecutor: WorkerExecutor, objectFactory: ObjectFactory ) : KotlinCompileCommon( - objectFactory.newInstance(KotlinMultiplatformCommonCompilerOptionsDefault::class.java), - workerExecutor, - objectFactory -), + objectFactory.newInstance(KotlinMultiplatformCommonCompilerOptionsDefault::class.java), + workerExecutor, + objectFactory + ), KspTask { // Overrding an internal function is hacky. @@ -376,11 +233,10 @@ abstract class KspTaskMetadata @Inject constructor( taskOutputsBackup: TaskOutputsBackup? ) { val changedFiles = getChangedFiles(inputChanges, incrementalProps) - if (!isKspIncremental || changedFiles.hasNonSourceChange()) { - clearIncCache() - } else { - args.addChangedFiles(changedFiles) + val extraOptions = incrementalChangesTransformers.get().flatMap { + it(changedFiles) } + args.addPluginOptions(extraOptions) args.expectActualLinker = true super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) } @@ -398,47 +254,12 @@ abstract class KspTaskNative @Inject internal constructor( 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 +internal fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value" - 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>) { +internal 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() 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 68258372..bb3ca97a 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 @@ -22,6 +22,9 @@ 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.FileCollection import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskProvider @@ -29,7 +32,13 @@ import org.gradle.api.tasks.compile.JavaCompile import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.process.CommandLineArgumentProvider import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry +import org.gradle.util.GradleVersion import org.jetbrains.kotlin.config.ApiVersion +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.CompilerPluginConfig import org.jetbrains.kotlin.gradle.plugin.FilesSubpluginOption import org.jetbrains.kotlin.gradle.plugin.InternalSubpluginOption @@ -50,6 +59,10 @@ 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.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.util.concurrent.Callable import javax.inject.Inject @@ -218,11 +231,10 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool val kotlinCompileTask = kotlinCompileProvider.get() + val processorClasspath = project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath") + .extendsFrom(*nonEmptyKspConfigurations.toTypedArray()) fun configureAsKspTask(kspTask: KspTask, isIncremental: Boolean) { // depends on the processor; if the processor changes, it needs to be reprocessed. - val processorClasspath = project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath") - .extendsFrom(*nonEmptyKspConfigurations.toTypedArray()) - kspTask.processorClasspath.from(processorClasspath) kspTask.dependsOn(processorClasspath.buildDependencies) kspTask.commandLineArgumentProviders.addAll(kspExtension.commandLineArgumentProviders) @@ -240,11 +252,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool ) } ) - kspTask.destination = kspOutputDir kspTask.inputs.property("apOptions", kspExtension.arguments) - kspTask.kspCacheDir.fileValue(getKspCachesDir(project, sourceSetName, target)).disallowChanges() - - kspTask.isKspIncremental = isIncremental } fun configureAsAbstractKotlinCompileTool(kspTask: AbstractKotlinCompileTool<*>) { @@ -312,12 +320,29 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool configureAsKspTask(kspTask, isIncremental) configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) configurePluginOptions(kspTask) - kspTask.configureClasspathSnapshot() - kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) + 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()) + kspTask.destination.value(kspOutputDir) + + val isIntermoduleIncremental = + (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && + isIncremental + val classStructureFiles = getClassStructureFiles(project, kspTask.libraries) + kspTask.incrementalChangesTransformers.add( + createIncrementalChangesTransformer( + isIncremental, + isIntermoduleIncremental, + getKspCachesDir(project, sourceSetName, target), + project.provider { classStructureFiles }, + project.provider { kspTask.libraries }, + project.provider { processorClasspath } + ) + ) } // Don't support binary generation for non-JVM platforms yet. // FIXME: figure out how to add user generated libraries. @@ -331,12 +356,24 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool 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.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 + + kspTask.incrementalChangesTransformers.add( + createIncrementalChangesTransformer( + isIncremental, + false, + getKspCachesDir(project, sourceSetName, target), + project.provider { project.files() }, + project.provider { project.files() }, + project.provider { project.files() }, + ) + ) } } } @@ -347,8 +384,21 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool configureAsKspTask(kspTask, isIncremental) configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) configurePluginOptions(kspTask) - kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) + kspTask.libraries.setFrom( + kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries }) + ) kspTask.compilerOptions.useK2.value(false) + + kspTask.incrementalChangesTransformers.add( + createIncrementalChangesTransformer( + isIncremental, + false, + getKspCachesDir(project, sourceSetName, target), + project.provider { project.files() }, + project.provider { project.files() }, + project.provider { project.files() }, + ) + ) } } } @@ -375,7 +425,6 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool kspOutputDir.deleteRecursively() } } - } } else -> return project.provider { emptyList() } @@ -427,8 +476,6 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool artifactId = KSP_COMPILER_PLUGIN_ID, version = KSP_VERSION ) - - val apiArtifact = "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION" } // Copied from kotlin-gradle-plugin, because they are internal. @@ -447,3 +494,162 @@ internal fun findJavaTaskForKotlinCompilation(compilation: KotlinCompilation<*>) is KotlinJvmCompilation -> compilation.compileJavaTaskProvider // may be null for Kotlin-only JVM target in MPP else -> null } + +internal val artifactType = Attribute.of("artifactType", String::class.java) + +internal 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 + } +} + +internal fun getClassStructureFiles( + project: Project, + libraries: ConfigurableFileCollection, +): FileCollection { + maybeRegisterTransform(project) + + val classStructureIfIncremental = project.configurations.detachedConfiguration( + project.dependencies.create(project.files(project.provider { libraries })) + ) + + return classStructureIfIncremental.incoming.artifactView { viewConfig -> + viewConfig.attributes.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + }.files +} + +// Reuse Kapt's infrastructure to compute affected names in classpath. +// This is adapted from KaptTask.findClasspathChanges. +internal fun findClasspathChanges( + changes: ChangedFiles, + cacheDir: File, + allDataFiles: Set<File>, + libs: List<File>, + processorCP: List<File>, +): KaptClasspathChanges { + cacheDir.mkdirs() + + 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, + libs, + processorCP, + allDataFiles + ) + + val classpathChanges = currentSnapshot.diff(previousSnapshot, changedFiles) + if (classpathChanges is KaptClasspathChanges.Unknown || changes is ChangedFiles.Unknown) { + cacheDir.deleteRecursively() + cacheDir.mkdirs() + } + currentSnapshot.writeToCache() + + return classpathChanges +} + +internal fun ChangedFiles.hasNonSourceChange(): Boolean { + if (this !is ChangedFiles.Known) + return true + + return !(this.modified + this.removed).all { + it.isKotlinFile(listOf("kt")) || it.isJavaFile() + } +} + +fun KaptClasspathChanges.toSubpluginOptions(): List<SubpluginOption> { + return if (this is KaptClasspathChanges.Known) { + this.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty { + listOf(SubpluginOption("changedClasses", joinToString(":"))) + } ?: emptyList() + } else { + emptyList() + } +} + +fun ChangedFiles.toSubpluginOptions(): List<SubpluginOption> { + return if (this is ChangedFiles.Known) { + val options = mutableListOf<SubpluginOption>() + this.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { + options += SubpluginOption("knownModified", map { it.path }.joinToString(File.pathSeparator)) + } + this.removed.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { + options += SubpluginOption("knownRemoved", map { it.path }.joinToString(File.pathSeparator)) + } + options + } else { + emptyList() + } +} + +// Return a closure that captures required arguments only. +internal fun createIncrementalChangesTransformer( + isKspIncremental: Boolean, + isIntermoduleIncremental: Boolean, + cacheDir: File, + classpathStructure: Provider<FileCollection>, + libraries: Provider<FileCollection>, + processorCP: Provider<FileCollection>, +): (ChangedFiles) -> List<SubpluginOption> = { changedFiles -> + val options = mutableListOf<SubpluginOption>() + if (isKspIncremental) { + if (isIntermoduleIncremental) { + // findClasspathChanges may clear caches, if there are + // 1. unknown changes, or + // 2. changes in annotation processors. + val classpathChanges = findClasspathChanges( + changedFiles, + cacheDir, + classpathStructure.get().files, + libraries.get().files.toList(), + processorCP.get().files.toList() + ) + options += classpathChanges.toSubpluginOptions() + } else { + if (changedFiles.hasNonSourceChange()) { + cacheDir.deleteRecursively() + } + } + } else { + cacheDir.deleteRecursively() + } + options += changedFiles.toSubpluginOptions() + + options +} |