KotlinCompile: Set up artifact transform to take classpath snapshot

This commit sets up the artifact transform only. The actual classpath
snapshotting and computation of classpath changes will be done later.

Bug: KT-45777
Test: Existing IncrementalCompilationClasspathSnapshotJvmMultiProjectIT
      and IncrementalJavaChangeClasspathSnapshotIT
This commit is contained in:
Hung Nguyen
2021-05-19 09:58:55 +01:00
committed by nataliya.valtman
parent 5517aa36e1
commit c8b3b6df9c
6 changed files with 199 additions and 20 deletions
@@ -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<KaptGenerateStubsTask> {
val properties = PropertiesProvider(project)
val kaptTaskName = getKaptTaskName("kaptGenerateStubs")
val kaptTaskProvider = project.registerTask<KaptGenerateStubsTask>(kaptTaskName)
val kaptTaskProvider = project.registerTask<KaptGenerateStubsTask>(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 {
@@ -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<KaptGenerateStubsTask>(kotlinCompilation) {
internal class Configurator(
private val kotlinCompileTaskProvider: TaskProvider<KotlinCompile>,
kotlinCompilation: KotlinCompilationData<*>,
properties: PropertiesProvider,
private val classpathSnapshotDir: File
) : KotlinCompile.Configurator<KaptGenerateStubsTask>(kotlinCompilation, properties) {
override fun getClasspathSnapshotDir(task: KaptGenerateStubsTask): Provider<Directory> =
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()
@@ -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<TransformParameters.None> {
@get:Classpath
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
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"
@@ -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
@@ -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<T : CommonCompilerArguments> : 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<Directory> =
task.project.layout.buildDirectory.dir("$KOTLIN_BUILD_DIR_NAME/${task.name}")
protected open fun getClasspathSnapshotDir(task: T): Provider<Directory> =
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<KotlinCompile>(kotlinCompilation) {
override fun configure(task: KotlinCompile) {
internal open class Configurator<T : KotlinCompile>(
kotlinCompilation: KotlinCompilationData<*>,
private val properties: PropertiesProvider
) : AbstractKotlinCompile.Configurator<T>(kotlinCompilation) {
companion object {
private const val TRANSFORMS_REGISTERED = "_kgp_internal_kotlin_compile_transforms_registered"
val ARTIFACT_TYPE_ATTRIBUTE: Attribute<String> = 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<T>, 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<Boolean>
@get:Nested
abstract val classpathSnapshotProperties: ClasspathSnapshotProperties
/** Properties related to the `kotlin.incremental.useClasspathSnapshot` feature. */
abstract class ClasspathSnapshotProperties {
@get:Input
abstract val useClasspathSnapshot: Property<Boolean>
@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<DefaultKotlinJavaToolchain> = 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<File>, 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<File> {
val subDirs = classpathSnapshotDir.listFiles() ?: return emptyList()
return subDirs.toList().sortedBy { it.name.toInt() }.map { File(it, CLASSPATH_ENTRY_SNAPSHOT_FILE_NAME) }
}
}
@CacheableTask
@@ -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<out KotlinCompile> {
val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<KotlinCompile, KotlinCompileWithWorkers>(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<KotlinCompile>(compilation, properties)
@Suppress("UNCHECKED_CAST")
configurator.runAtConfigurationTime(kotlinCompile as TaskProvider<KotlinCompile>, 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(