diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt index 6853d586391..e9d8430891c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt @@ -223,6 +223,8 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To private fun Kapt3SubpluginContext.getKaptIncrementalDataDir() = temporaryKaptDirectory("incrementalData") + private fun Kapt3SubpluginContext.getKaptClasspathSnapshotDir() = temporaryKaptDirectory("classpath-snapshot") + private fun Kapt3SubpluginContext.getKaptIncrementalAnnotationProcessingCache() = temporaryKaptDirectory("incApCache") private fun Kapt3SubpluginContext.temporaryKaptDirectory( @@ -645,10 +647,20 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To } private fun Kapt3SubpluginContext.createKaptGenerateStubsTask(): TaskProvider { + val properties = PropertiesProvider(project) val kaptTaskName = getKaptTaskName("kaptGenerateStubs") + val kaptTaskProvider = project.registerTask(kaptTaskName) - val kaptTaskProvider = project.registerTask(kaptTaskName) { kaptTask -> - KaptGenerateStubsTask.Configurator(kotlinCompile.get(), kotlinCompilation).configure(kaptTask) + val configurator = KaptGenerateStubsTask.Configurator( + kotlinCompile, + kotlinCompilation, + properties, + getKaptClasspathSnapshotDir() + ) + configurator.runAtConfigurationTime(kaptTaskProvider, project) + + kaptTaskProvider.configure { kaptTask -> + configurator.configure(kaptTask) kaptTask.stubsDir.set(getKaptStubsDir()) kaptTask.destinationDirectory.set(getKaptIncrementalDataDir()) @@ -656,7 +668,7 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To kaptTask.kaptClasspath.from(kaptClasspathConfigurations) - PropertiesProvider(project).mapKotlinTaskProperties(kaptTask) + properties.mapKotlinTaskProperties(kaptTask) if (!includeCompileClasspath) { kaptTask.onlyIf { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt index ae583a5d131..24b2846f050 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptGenerateStubsTask.kt @@ -17,15 +17,17 @@ package org.jetbrains.kotlin.gradle.internal import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider import org.gradle.api.tasks.* import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData -import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile +import org.jetbrains.kotlin.gradle.tasks.* import org.jetbrains.kotlin.gradle.tasks.FilteringSourceRootsContainer -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.SourceRoots import org.jetbrains.kotlin.gradle.utils.isParentOf import org.jetbrains.kotlin.incremental.classpathAsList @@ -36,11 +38,20 @@ import java.util.concurrent.Callable @CacheableTask abstract class KaptGenerateStubsTask : KotlinCompile(KotlinJvmOptionsImpl()) { - class Configurator(private val kotlinCompileTask: KotlinCompile, kotlinCompilation: KotlinCompilationData<*>) : - AbstractKotlinCompile.Configurator(kotlinCompilation) { + internal class Configurator( + private val kotlinCompileTaskProvider: TaskProvider, + kotlinCompilation: KotlinCompilationData<*>, + properties: PropertiesProvider, + private val classpathSnapshotDir: File + ) : KotlinCompile.Configurator(kotlinCompilation, properties) { + + override fun getClasspathSnapshotDir(task: KaptGenerateStubsTask): Provider = + task.project.objects.directoryProperty().dir(classpathSnapshotDir.path) override fun configure(task: KaptGenerateStubsTask) { super.configure(task) + + val kotlinCompileTask = kotlinCompileTaskProvider.get() val providerFactory = kotlinCompileTask.project.providers task.useModuleDetection.value(kotlinCompileTask.useModuleDetection).disallowChanges() task.moduleName.value(kotlinCompileTask.moduleName).disallowChanges() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/transforms/ClasspathEntrySnapshotTransform.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/transforms/ClasspathEntrySnapshotTransform.kt new file mode 100644 index 00000000000..7ee258de00c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/transforms/ClasspathEntrySnapshotTransform.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.internal.transforms + +import org.gradle.api.artifacts.transform.* +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Classpath +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile.Companion.CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE + +/** Transform to create a snapshot ([CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE]) of a classpath entry (directory or jar). */ +@CacheableTransform +abstract class ClasspathEntrySnapshotTransform : TransformAction { + + @get:Classpath + @get:InputArtifact + abstract val inputArtifact: Provider + + override fun transform(outputs: TransformOutputs) { + val classpathEntry = inputArtifact.get().asFile + val snapshotFile = outputs.file(CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME) + + // TODO WORK-IN-PROGRESS + val snapshot = "[Place holder - actual snapshot will be inserted in an upcoming change]" + snapshotFile.writeText(snapshot) + } +} + +const val CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME = "classpath-entry-snapshot.bin" diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt index 50430da6ac4..f3e50bf6aef 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt @@ -37,7 +37,7 @@ internal fun PropertiesProvider.mapKotlinTaskProperties(task: AbstractKotlinComp usePreciseJavaTracking?.let { task.usePreciseJavaTracking = it } - task.useClasspathSnapshot.value(useClasspathSnapshot).disallowChanges() + task.classpathSnapshotProperties.useClasspathSnapshot.value(useClasspathSnapshot).disallowChanges() useFir?.let { if (it == true) { task.kotlinOptions.useFir = true diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index cf16067300b..1b1e3da86bd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -10,6 +10,7 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.file.* import org.gradle.api.invocation.Gradle +import org.gradle.api.attributes.Attribute import org.gradle.api.logging.Logger import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty @@ -43,6 +44,8 @@ import org.jetbrains.kotlin.gradle.internal.* import org.jetbrains.kotlin.gradle.internal.tasks.TaskConfigurator import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles +import org.jetbrains.kotlin.gradle.internal.transforms.CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME +import org.jetbrains.kotlin.gradle.internal.transforms.ClasspathEntrySnapshotTransform import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector import org.jetbrains.kotlin.gradle.logging.kotlinDebug @@ -193,13 +196,17 @@ abstract class AbstractKotlinCompile : AbstractKotl project.plugins.any { it is KotlinPlatformPluginBase || it is KotlinMultiplatformPluginWrapper || it is KotlinPm20PluginWrapper } } ).disallowChanges() - task.taskBuildDirectory.value( - project.layout.buildDirectory.dir("$KOTLIN_BUILD_DIR_NAME/${task.name}") - ).disallowChanges() + task.taskBuildDirectory.value(getKotlinBuildDir(task)).disallowChanges() task.localStateDirectories.from(task.taskBuildDirectory).disallowChanges() PropertiesProvider(task.project).mapKotlinDaemonProperties(task) } + + private fun getKotlinBuildDir(task: T): Provider = + task.project.layout.buildDirectory.dir("$KOTLIN_BUILD_DIR_NAME/${task.name}") + + protected open fun getClasspathSnapshotDir(task: T): Provider = + task.project.layout.buildDirectory.dir("$KOTLIN_BUILD_DIR_NAME/classpath-snapshot/${task.name}") } init { @@ -432,8 +439,53 @@ abstract class KotlinCompile @Inject constructor( KotlinJvmCompile, UsesKotlinJavaToolchain { - class Configurator(kotlinCompilation: KotlinCompilationData<*>) : AbstractKotlinCompile.Configurator(kotlinCompilation) { - override fun configure(task: KotlinCompile) { + internal open class Configurator( + kotlinCompilation: KotlinCompilationData<*>, + private val properties: PropertiesProvider + ) : AbstractKotlinCompile.Configurator(kotlinCompilation) { + + companion object { + private const val TRANSFORMS_REGISTERED = "_kgp_internal_kotlin_compile_transforms_registered" + + val ARTIFACT_TYPE_ATTRIBUTE: Attribute = Attribute.of("artifactType", String::class.java) + private const val DIRECTORY_ARTIFACT_TYPE = "directory" + private const val JAR_ARTIFACT_TYPE = "jar" + const val CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE = "classpath-entry-snapshot" + } + + /** + * Prepares for configuration of the task. This method must be called during build configuration, not during task configuration + * (which typically happens after build configuration). The reason is that some actions must be performed early (e.g., creating + * configurations should be done early to avoid issues with composite builds (https://issuetracker.google.com/183952598)). + */ + fun runAtConfigurationTime(taskProvider: TaskProvider, project: Project) { + if (properties.useClasspathSnapshot) { + registerTransformsOnce(project) + project.configurations.create(classpathSnapshotConfigurationName(taskProvider.name)).apply { + project.dependencies.add(name, project.files(project.provider { taskProvider.get().classpath })) + } + } + } + + private fun registerTransformsOnce(project: Project) { + if (project.extensions.extraProperties.has(TRANSFORMS_REGISTERED)) { + return + } + project.extensions.extraProperties[TRANSFORMS_REGISTERED] = true + + project.dependencies.registerTransform(ClasspathEntrySnapshotTransform::class.java) { + it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, JAR_ARTIFACT_TYPE) + it.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE) + } + project.dependencies.registerTransform(ClasspathEntrySnapshotTransform::class.java) { + it.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, DIRECTORY_ARTIFACT_TYPE) + it.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE) + } + } + + private fun classpathSnapshotConfigurationName(taskName: String) = "_kgp_internal_${taskName}_classpath_snapshot" + + override fun configure(task: T) { super.configure(task) val compileJavaTaskProvider = when (compilation) { @@ -451,6 +503,16 @@ abstract class KotlinCompile @Inject constructor( compileJavaTaskProvider.map { it.name } ) } + + if (properties.useClasspathSnapshot) { + val classpathSnapshot = task.project.configurations.getByName(classpathSnapshotConfigurationName(task.name)) + task.classpathSnapshotProperties.classpathSnapshot.from( + classpathSnapshot.incoming.artifactView { + it.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, CLASSPATH_ENTRY_SNAPSHOT_ARTIFACT_TYPE) + }.files + ) + task.classpathSnapshotProperties.classpathSnapshotDir.value(getClasspathSnapshotDir(task)).disallowChanges() + } } } @@ -481,8 +543,22 @@ abstract class KotlinCompile @Inject constructor( logger.kotlinDebug { "Set $this.usePreciseJavaTracking=$value" } } - @get:Input - abstract val useClasspathSnapshot: Property + @get:Nested + abstract val classpathSnapshotProperties: ClasspathSnapshotProperties + + /** Properties related to the `kotlin.incremental.useClasspathSnapshot` feature. */ + abstract class ClasspathSnapshotProperties { + @get:Input + abstract val useClasspathSnapshot: Property + + @get:Classpath + @get:Optional // Set if useClasspathSnapshot == true + abstract val classpathSnapshot: ConfigurableFileCollection + + @get:OutputDirectory + @get:Optional // Set if useClasspathSnapshot == true + abstract val classpathSnapshotDir: DirectoryProperty + } @get:Internal internal val defaultKotlinJavaToolchain: Provider = objects @@ -551,6 +627,9 @@ abstract class KotlinCompile @Inject constructor( val outputItemCollector = OutputItemsCollectorImpl() val compilerRunner = compilerRunner.get() + if (classpathSnapshotProperties.useClasspathSnapshot.get()) { + getClasspathChanges() + } val icEnv = if (isIncrementalCompilationEnabled()) { logger.info(USING_JVM_INCREMENTAL_COMPILATION_MESSAGE) IncrementalCompilationEnvironment( @@ -578,6 +657,12 @@ abstract class KotlinCompile @Inject constructor( environment, defaultKotlinJavaToolchain.get().providedJvm.get().javaHome ) + + with(classpathSnapshotProperties) { + if (useClasspathSnapshot.get()) { + copyClasspathSnapshotFilesToDir(classpathSnapshot.files.toList(), classpathSnapshotDir.get().asFile) + } + } } private fun validateKotlinAndJavaHasSameTargetCompatibility(args: K2JVMCompilerArguments) { @@ -636,6 +721,39 @@ abstract class KotlinCompile @Inject constructor( sourceRootsContainer.add(*sources) return super.source(*sources) } + + private fun getClasspathChanges() { + // TODO WORK-IN-PROGRESS + val currentClasspathSnapshotFiles = classpathSnapshotProperties.classpathSnapshot.files.toList() + val previousClasspathSnapshotFiles = getClasspathSnapshotFilesInDir(classpathSnapshotProperties.classpathSnapshotDir.get().asFile) + } + + /** + * Copies classpath snapshot files to the given directory. + * + * To preserve their order, we put them in subdirectories with names being their indices in the original list, as shown below: + * classpathSnapshotDir/0/snapshotFileName + * classpathSnapshotDir/1/snapshotFileName + * ... + * classpathSnapshotDir/N-1/snapshotFileName + */ + private fun copyClasspathSnapshotFilesToDir(classpathSnapshotFiles: List, classpathSnapshotDir: File) { + classpathSnapshotDir.deleteRecursively() + classpathSnapshotDir.mkdirs() + for ((index, file) in classpathSnapshotFiles.withIndex()) { + file.copyTo(File("$classpathSnapshotDir/$index/$CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME"), overwrite = true) + } + } + + /** + * Returns all classpath snapshot files in the given directory, sorted by their original indices (the subdirectories' names). + * + * See [copyClasspathSnapshotFilesToDir] for the structure of the classpath snapshot directory. + */ + private fun getClasspathSnapshotFilesInDir(classpathSnapshotDir: File): List { + val subDirs = classpathSnapshotDir.listFiles() ?: return emptyList() + return subDirs.toList().sortedBy { it.name.toInt() }.map { File(it, CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME) } + } } @CacheableTask diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksProvider.kt index 90cac9a1095..ca0a8b88dff 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/TasksProvider.kt @@ -21,7 +21,6 @@ import org.gradle.api.Task import org.gradle.api.UnknownTaskException import org.gradle.api.tasks.TaskCollection import org.gradle.api.tasks.TaskProvider -import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.mapKotlinTaskProperties import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData @@ -89,12 +88,19 @@ internal open class KotlinTasksProvider { ): TaskProvider { val properties = PropertiesProvider(project) val taskClass = taskOrWorkersTask(properties) - val result = project.registerTask(name, taskClass, constructorArgs = listOf(compilation.kotlinOptions)) { + val kotlinCompile = project.registerTask(name, taskClass, constructorArgs = listOf(compilation.kotlinOptions)) + + val configurator = KotlinCompile.Configurator(compilation, properties) + @Suppress("UNCHECKED_CAST") + configurator.runAtConfigurationTime(kotlinCompile as TaskProvider, project) + + kotlinCompile.configure { configureAction(it) - KotlinCompile.Configurator(compilation).configure(it) + configurator.configure(it) } - configure(result, project, properties, compilation) - return result + configure(kotlinCompile, project, properties, compilation) + + return kotlinCompile } fun registerKotlinJSTask(