KT-45777: Move classpath diffing to incremental Kotlin compiler (2/2)
as we need access to the lookup tracker to compute classpath changes more efficiently and reduce the size of the saved classpath snapshot. The previous commit only changed the files' paths, this commit actually updates the files' contents. Note that classpath snapshotting still happens in Gradle artifact transforms. (However, the previous commit also moved the code for classpath snapshotting together with the code for classpath diffing as they are closely related.)
This commit is contained in:
committed by
teamcityserver
parent
dfaf195e1d
commit
f52be5f471
@@ -18,6 +18,7 @@ enum class BuildAttributeKind : Serializable {
|
|||||||
enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: String) : Serializable {
|
enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: String) : Serializable {
|
||||||
NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON, "Build history file not found"),
|
NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON, "Build history file not found"),
|
||||||
NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON, "ABI snapshot not found"),
|
NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON, "ABI snapshot not found"),
|
||||||
|
CLASSPATH_SNAPSHOT_NOT_FOUND(BuildAttributeKind.REBUILD_REASON, "Classpath snapshot not found"),
|
||||||
CACHE_CORRUPTION(BuildAttributeKind.REBUILD_REASON, "Cache corrupted"),
|
CACHE_CORRUPTION(BuildAttributeKind.REBUILD_REASON, "Cache corrupted"),
|
||||||
UNKNOWN_CHANGES_IN_GRADLE_INPUTS(BuildAttributeKind.REBUILD_REASON, "Unknown Gradle changes"),
|
UNKNOWN_CHANGES_IN_GRADLE_INPUTS(BuildAttributeKind.REBUILD_REASON, "Unknown Gradle changes"),
|
||||||
JAVA_CHANGE_UNTRACKED_FILE_IS_REMOVED(BuildAttributeKind.REBUILD_REASON, "Untracked Java file is removed"),
|
JAVA_CHANGE_UNTRACKED_FILE_IS_REMOVED(BuildAttributeKind.REBUILD_REASON, "Untracked Java file is removed"),
|
||||||
|
|||||||
+7
-3
@@ -10,9 +10,13 @@ import java.io.Serializable
|
|||||||
|
|
||||||
@Suppress("Reformat")
|
@Suppress("Reformat")
|
||||||
enum class BuildPerformanceMetric(val parent: BuildPerformanceMetric? = null, val readableString: String) : Serializable {
|
enum class BuildPerformanceMetric(val parent: BuildPerformanceMetric? = null, val readableString: String) : Serializable {
|
||||||
OUTPUT_SIZE(readableString = "Total output size"),
|
CACHE_DIRECTORY_SIZE(readableString = "Total size of the cache directory"),
|
||||||
LOOKUP_SIZE(OUTPUT_SIZE, "Lookups size"),
|
LOOKUP_SIZE(CACHE_DIRECTORY_SIZE, "Lookups size"),
|
||||||
SNAPSHOT_SIZE(OUTPUT_SIZE, "ABI snapshot size"),
|
SNAPSHOT_SIZE(CACHE_DIRECTORY_SIZE, "ABI snapshot size"),
|
||||||
|
|
||||||
|
// Metrics for the `kotlin.incremental.useClasspathSnapshot` feature
|
||||||
|
ORIGINAL_CLASSPATH_SNAPSHOT_SIZE(parent = null, "Size of the original classpath snapshot (before shrinking)"),
|
||||||
|
SHRUNK_CLASSPATH_SNAPSHOT_SIZE(parent = null, "Size of the shrunk classpath snapshot"),
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ enum class BuildTime(val parent: BuildTime? = null, val readableString: String)
|
|||||||
SET_UP_ABI_SNAPSHOTS(JAR_SNAPSHOT, "Set up ABI snapshot"),
|
SET_UP_ABI_SNAPSHOTS(JAR_SNAPSHOT, "Set up ABI snapshot"),
|
||||||
IC_ANALYZE_JAR_FILES(JAR_SNAPSHOT, "Analyze jar files"),
|
IC_ANALYZE_JAR_FILES(JAR_SNAPSHOT, "Analyze jar files"),
|
||||||
IC_CALCULATE_INITIAL_DIRTY_SET(INCREMENTAL_COMPILATION, "Init dirty symbols set"),
|
IC_CALCULATE_INITIAL_DIRTY_SET(INCREMENTAL_COMPILATION, "Init dirty symbols set"),
|
||||||
|
COMPUTE_CLASSPATH_CHANGES(IC_CALCULATE_INITIAL_DIRTY_SET, "Compute classpath changes"),
|
||||||
|
LOAD_CURRENT_CLASSPATH_SNAPSHOT(COMPUTE_CLASSPATH_CHANGES, "Load current classpath snapshot"),
|
||||||
|
SHRINK_CURRENT_CLASSPATH_SNAPSHOT(COMPUTE_CLASSPATH_CHANGES, "Shrink current classpath snapshot"),
|
||||||
|
LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT(COMPUTE_CLASSPATH_CHANGES, "Load shrunk previous classpath snapshot"),
|
||||||
|
COMPUTE_CHANGED_AND_IMPACTED_SET(COMPUTE_CLASSPATH_CHANGES, "Compute changed and impacted set"),
|
||||||
|
COMPUTE_CLASS_CHANGES(COMPUTE_CHANGED_AND_IMPACTED_SET, "Compute class changes"),
|
||||||
|
COMPUTE_KOTLIN_CLASS_CHANGES(COMPUTE_CLASS_CHANGES, "Compute Kotlin class changes"),
|
||||||
|
COMPUTE_JAVA_CLASS_CHANGES(COMPUTE_CLASS_CHANGES, "Compute Java class changes"),
|
||||||
|
COMPUTE_IMPACTED_SET(COMPUTE_CHANGED_AND_IMPACTED_SET, "Compute impacted set"),
|
||||||
IC_ANALYZE_CHANGES_IN_DEPENDENCIES(IC_CALCULATE_INITIAL_DIRTY_SET, "Analyze dependency changes"),
|
IC_ANALYZE_CHANGES_IN_DEPENDENCIES(IC_CALCULATE_INITIAL_DIRTY_SET, "Analyze dependency changes"),
|
||||||
IC_FIND_HISTORY_FILES(IC_ANALYZE_CHANGES_IN_DEPENDENCIES, "Find history files"),
|
IC_FIND_HISTORY_FILES(IC_ANALYZE_CHANGES_IN_DEPENDENCIES, "Find history files"),
|
||||||
IC_ANALYZE_HISTORY_FILES(IC_ANALYZE_CHANGES_IN_DEPENDENCIES, "Analyze history files"),
|
IC_ANALYZE_HISTORY_FILES(IC_ANALYZE_CHANGES_IN_DEPENDENCIES, "Analyze history files"),
|
||||||
@@ -38,6 +47,14 @@ enum class BuildTime(val parent: BuildTime? = null, val readableString: String)
|
|||||||
INCREMENTAL_ITERATION(INCREMENTAL_COMPILATION, "Incremental iteration"),
|
INCREMENTAL_ITERATION(INCREMENTAL_COMPILATION, "Incremental iteration"),
|
||||||
NON_INCREMENTAL_ITERATION(INCREMENTAL_COMPILATION, "Non-incremental iteration"),
|
NON_INCREMENTAL_ITERATION(INCREMENTAL_COMPILATION, "Non-incremental iteration"),
|
||||||
IC_WRITE_HISTORY_FILE(INCREMENTAL_COMPILATION, "Write history file"),
|
IC_WRITE_HISTORY_FILE(INCREMENTAL_COMPILATION, "Write history file"),
|
||||||
|
SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION(INCREMENTAL_COMPILATION, "Save shrunk current classpath snapshot after compilation"),
|
||||||
|
LOAD_CLASSPATH_SNAPSHOT(SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION, "Load classpath snapshot"),
|
||||||
|
SHRINK_CLASSPATH_SNAPSHOT(SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION, "Shrink classpath snapshot"),
|
||||||
|
GET_NON_DUPLICATE_CLASSES(SHRINK_CLASSPATH_SNAPSHOT, "Get non-duplicate classes"),
|
||||||
|
GET_LOOKUP_SYMBOLS(SHRINK_CLASSPATH_SNAPSHOT, "Get lookup symbols"),
|
||||||
|
FIND_REFERENCED_CLASSES(SHRINK_CLASSPATH_SNAPSHOT, "Find referenced classes"),
|
||||||
|
FIND_TRANSITIVELY_REFERENCED_CLASSES(SHRINK_CLASSPATH_SNAPSHOT, "Find transitively referenced classes"),
|
||||||
|
SAVE_SHRUNK_CLASSPATH_SNAPSHOT(SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION, "Save shrunk classpath snapshot"),
|
||||||
COMPILER_PERFORMANCE(readableString = "Compiler time"),
|
COMPILER_PERFORMANCE(readableString = "Compiler time"),
|
||||||
COMPILER_INITIALIZATION(COMPILER_PERFORMANCE, "Compiler initialization time"),
|
COMPILER_INITIALIZATION(COMPILER_PERFORMANCE, "Compiler initialization time"),
|
||||||
CODE_ANALYSIS(COMPILER_PERFORMANCE, "Compiler code analyse"),
|
CODE_ANALYSIS(COMPILER_PERFORMANCE, "Compiler code analyse"),
|
||||||
|
|||||||
@@ -5,70 +5,43 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.incremental
|
package org.jetbrains.kotlin.incremental
|
||||||
|
|
||||||
import com.intellij.util.io.DataExternalizer
|
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled
|
||||||
import org.jetbrains.kotlin.incremental.storage.FqNameExternalizer
|
import java.io.File
|
||||||
import org.jetbrains.kotlin.incremental.storage.SetExternalizer
|
import java.io.Serializable
|
||||||
import org.jetbrains.kotlin.incremental.storage.LookupSymbolExternalizer
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import java.io.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes to the classpath of the `KotlinCompile` task, used to compute the source files that need to be recompiled during an incremental
|
* Changes to the classpath of the `KotlinCompile` task, or information to compute them later by the Kotlin incremental compiler (see
|
||||||
* run.
|
* [ClasspathSnapshotEnabled.ToBeComputedByIncrementalCompiler].
|
||||||
*/
|
*/
|
||||||
sealed class ClasspathChanges : Serializable {
|
sealed class ClasspathChanges : Serializable {
|
||||||
|
|
||||||
class Available() : ClasspathChanges() {
|
sealed class ClasspathSnapshotEnabled : ClasspathChanges() {
|
||||||
|
|
||||||
companion object {
|
abstract val classpathSnapshotFiles: ClasspathSnapshotFiles
|
||||||
private const val serialVersionUID = 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
lateinit var lookupSymbols: Set<LookupSymbol> // Preferably ordered but not required
|
class Empty(override val classpathSnapshotFiles: ClasspathSnapshotFiles) : ClasspathSnapshotEnabled()
|
||||||
private set
|
|
||||||
|
|
||||||
lateinit var fqNames: Set<FqName> // Preferably ordered but not required
|
class ToBeComputedByIncrementalCompiler(override val classpathSnapshotFiles: ClasspathSnapshotFiles) : ClasspathSnapshotEnabled()
|
||||||
private set
|
|
||||||
|
|
||||||
constructor(lookupSymbols: Set<LookupSymbol>, fqNames: Set<FqName>) : this() {
|
class NotAvailableDueToMissingClasspathSnapshot(override val classpathSnapshotFiles: ClasspathSnapshotFiles) :
|
||||||
this.lookupSymbols = lookupSymbols
|
ClasspathSnapshotEnabled()
|
||||||
this.fqNames = fqNames
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeObject(output: ObjectOutputStream) {
|
class NotAvailableForNonIncrementalRun(override val classpathSnapshotFiles: ClasspathSnapshotFiles) : ClasspathSnapshotEnabled()
|
||||||
// Can't close DataOutputStream below as it will also close the underlying ObjectOutputStream, which is still in use.
|
|
||||||
ClasspathChangesAvailableExternalizer.save(DataOutputStream(output), this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readObject(input: ObjectInputStream) {
|
|
||||||
// Can't close DataInputStream below as it will also close the underlying ObjectInputStream, which is still in use.
|
|
||||||
ClasspathChangesAvailableExternalizer.read(DataInputStream(input)).also {
|
|
||||||
lookupSymbols = it.lookupSymbols
|
|
||||||
fqNames = it.fqNames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class NotAvailable : ClasspathChanges() {
|
object ClasspathSnapshotDisabled : ClasspathChanges()
|
||||||
object UnableToCompute : NotAvailable()
|
|
||||||
object ForNonIncrementalRun : NotAvailable()
|
object NotAvailableForJSCompiler : ClasspathChanges()
|
||||||
object ClasspathSnapshotIsDisabled : NotAvailable()
|
|
||||||
object ReservedForTestsOnly : NotAvailable()
|
|
||||||
object ForJSCompiler : NotAvailable()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private object ClasspathChangesAvailableExternalizer : DataExternalizer<ClasspathChanges.Available> {
|
class ClasspathSnapshotFiles(
|
||||||
|
val currentClasspathEntrySnapshotFiles: List<File>,
|
||||||
|
classpathSnapshotDir: File
|
||||||
|
) : Serializable {
|
||||||
|
|
||||||
override fun save(output: DataOutput, classpathChanges: ClasspathChanges.Available) {
|
val shrunkPreviousClasspathSnapshotFile: File = File(classpathSnapshotDir, "shrunk-classpath-snapshot.bin")
|
||||||
SetExternalizer(LookupSymbolExternalizer).save(output, classpathChanges.lookupSymbols)
|
|
||||||
SetExternalizer(FqNameExternalizer).save(output, classpathChanges.fqNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(input: DataInput): ClasspathChanges.Available {
|
companion object {
|
||||||
return ClasspathChanges.Available(
|
private const val serialVersionUID = 0L
|
||||||
lookupSymbols = SetExternalizer(LookupSymbolExternalizer).read(input),
|
|
||||||
fqNames = SetExternalizer(FqNameExternalizer).read(input)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ import java.util.*
|
|||||||
|
|
||||||
open class LookupStorage(
|
open class LookupStorage(
|
||||||
targetDataDir: File,
|
targetDataDir: File,
|
||||||
pathConverter: FileToPathConverter
|
pathConverter: FileToPathConverter,
|
||||||
|
storeFullFqNames: Boolean = false
|
||||||
) : BasicMapsOwner(targetDataDir) {
|
) : BasicMapsOwner(targetDataDir) {
|
||||||
val LOG = Logger.getInstance("#org.jetbrains.kotlin.jps.build.KotlinBuilder")
|
val LOG = Logger.getInstance("#org.jetbrains.kotlin.jps.build.KotlinBuilder")
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ open class LookupStorage(
|
|||||||
private val countersFile = "counters".storageFile
|
private val countersFile = "counters".storageFile
|
||||||
private val idToFile = registerMap(IdToFileMap("id-to-file".storageFile, pathConverter))
|
private val idToFile = registerMap(IdToFileMap("id-to-file".storageFile, pathConverter))
|
||||||
private val fileToId = registerMap(FileToIdMap("file-to-id".storageFile, pathConverter))
|
private val fileToId = registerMap(FileToIdMap("file-to-id".storageFile, pathConverter))
|
||||||
val lookupMap = registerMap(LookupMap("lookups".storageFile))
|
val lookupMap = registerMap(LookupMap("lookups".storageFile, storeFullFqNames))
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var size: Int = 0
|
private var size: Int = 0
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ package org.jetbrains.kotlin.incremental.storage
|
|||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class LookupMap(storage: File) : BasicMap<LookupSymbolKey, Collection<Int>>(storage, LookupSymbolKeyDescriptor, IntCollectionExternalizer) {
|
class LookupMap(storage: File, storeFullFqNames: Boolean) :
|
||||||
|
BasicMap<LookupSymbolKey, Collection<Int>>(storage, LookupSymbolKeyDescriptor(storeFullFqNames), IntCollectionExternalizer) {
|
||||||
|
|
||||||
override fun dumpKey(key: LookupSymbolKey): String = key.toString()
|
override fun dumpKey(key: LookupSymbolKey): String = key.toString()
|
||||||
|
|
||||||
override fun dumpValue(value: Collection<Int>): String = value.toString()
|
override fun dumpValue(value: Collection<Int>): String = value.toString()
|
||||||
|
|||||||
@@ -22,22 +22,21 @@ import com.intellij.util.io.DataExternalizer
|
|||||||
import com.intellij.util.io.EnumeratorStringDescriptor
|
import com.intellij.util.io.EnumeratorStringDescriptor
|
||||||
import com.intellij.util.io.IOUtil
|
import com.intellij.util.io.IOUtil
|
||||||
import com.intellij.util.io.KeyDescriptor
|
import com.intellij.util.io.KeyDescriptor
|
||||||
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
|
|
||||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
|
||||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import java.io.DataInput
|
import java.io.*
|
||||||
import java.io.DataInputStream
|
|
||||||
import java.io.DataOutput
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Storage versioning:
|
* Storage versioning:
|
||||||
* 0 - only name and value hashes are saved
|
* 0 - only name and value hashes are saved
|
||||||
* 1 - name and scope are saved
|
* 1 - name and scope are saved
|
||||||
*/
|
*/
|
||||||
object LookupSymbolKeyDescriptor : KeyDescriptor<LookupSymbolKey> {
|
class LookupSymbolKeyDescriptor(
|
||||||
|
/** If `true`, original values are saved; if `false`, only hashes are saved. */
|
||||||
|
private val storeFullFqNames: Boolean = false
|
||||||
|
) : KeyDescriptor<LookupSymbolKey> {
|
||||||
|
|
||||||
override fun read(input: DataInput): LookupSymbolKey {
|
override fun read(input: DataInput): LookupSymbolKey {
|
||||||
val version = input.readByte()
|
val version = input.readByte()
|
||||||
return when (version.toInt()) {
|
return when (version.toInt()) {
|
||||||
@@ -51,14 +50,12 @@ object LookupSymbolKeyDescriptor : KeyDescriptor<LookupSymbolKey> {
|
|||||||
val second = input.readInt()
|
val second = input.readInt()
|
||||||
LookupSymbolKey(first, second, "", "")
|
LookupSymbolKey(first, second, "", "")
|
||||||
}
|
}
|
||||||
else -> throw RuntimeException("Unknown version of LookupSymbolKeyDescriptor=${version}")
|
else -> throw IllegalArgumentException("Unknown version of LookupSymbolKeyDescriptor=${version}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val storeFullFqName = CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_CLASSPATH_SNAPSHOTS.value.toBooleanLenient() ?: false
|
|
||||||
|
|
||||||
override fun save(output: DataOutput, value: LookupSymbolKey) {
|
override fun save(output: DataOutput, value: LookupSymbolKey) {
|
||||||
if (storeFullFqName) {
|
if (storeFullFqNames) {
|
||||||
output.writeByte(0)
|
output.writeByte(0)
|
||||||
output.writeUTF(value.name)
|
output.writeUTF(value.name)
|
||||||
output.writeUTF(value.scope)
|
output.writeUTF(value.scope)
|
||||||
@@ -74,18 +71,6 @@ object LookupSymbolKeyDescriptor : KeyDescriptor<LookupSymbolKey> {
|
|||||||
override fun isEqual(val1: LookupSymbolKey, val2: LookupSymbolKey): Boolean = val1 == val2
|
override fun isEqual(val1: LookupSymbolKey, val2: LookupSymbolKey): Boolean = val1 == val2
|
||||||
}
|
}
|
||||||
|
|
||||||
object LookupSymbolExternalizer : DataExternalizer<LookupSymbol> {
|
|
||||||
|
|
||||||
override fun save(output: DataOutput, lookupSymbol: LookupSymbol) {
|
|
||||||
output.writeString(lookupSymbol.name)
|
|
||||||
output.writeString(lookupSymbol.scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(input: DataInput): LookupSymbol {
|
|
||||||
return LookupSymbol(name = input.readString(), scope = input.readString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object FqNameExternalizer : DataExternalizer<FqName> {
|
object FqNameExternalizer : DataExternalizer<FqName> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, fqName: FqName) {
|
override fun save(output: DataOutput, fqName: FqName) {
|
||||||
@@ -226,6 +211,32 @@ object ConstantExternalizer : DataExternalizer<Any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> DataExternalizer<T>.saveToFile(file: File, value: T) {
|
||||||
|
return DataOutputStream(FileOutputStream(file).buffered()).use {
|
||||||
|
save(it, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> DataExternalizer<T>.loadFromFile(file: File): T {
|
||||||
|
return DataInputStream(FileInputStream(file).buffered()).use {
|
||||||
|
read(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> DataExternalizer<T>.toByteArray(value: T): ByteArray {
|
||||||
|
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||||
|
DataOutputStream(byteArrayOutputStream.buffered()).use {
|
||||||
|
save(it, value)
|
||||||
|
}
|
||||||
|
return byteArrayOutputStream.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> DataExternalizer<T>.fromByteArray(byteArray: ByteArray): T {
|
||||||
|
return DataInputStream(ByteArrayInputStream(byteArray).buffered()).use {
|
||||||
|
read(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object IntExternalizer : DataExternalizer<Int> {
|
object IntExternalizer : DataExternalizer<Int> {
|
||||||
override fun save(output: DataOutput, value: Int) = output.writeInt(value)
|
override fun save(output: DataOutput, value: Int) = output.writeInt(value)
|
||||||
override fun read(input: DataInput): Int = input.readInt()
|
override fun read(input: DataInput): Int = input.readInt()
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.incremental.storage
|
|||||||
|
|
||||||
import com.intellij.util.containers.MultiMap
|
import com.intellij.util.containers.MultiMap
|
||||||
import org.jetbrains.kotlin.TestWithWorkingDir
|
import org.jetbrains.kotlin.TestWithWorkingDir
|
||||||
|
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
|
||||||
|
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||||
import org.jetbrains.kotlin.incremental.LookupStorage
|
import org.jetbrains.kotlin.incremental.LookupStorage
|
||||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||||
import org.jetbrains.kotlin.incremental.testingUtils.assertEqualDirectories
|
import org.jetbrains.kotlin.incremental.testingUtils.assertEqualDirectories
|
||||||
@@ -48,7 +50,11 @@ class RelocatableCachesTest : TestWithWorkingDir() {
|
|||||||
private fun fillLookupStorage(projectRoot: File, reverseFiles: Boolean, reverseLookups: Boolean) {
|
private fun fillLookupStorage(projectRoot: File, reverseFiles: Boolean, reverseLookups: Boolean) {
|
||||||
val storageRoot = projectRoot.storageRoot
|
val storageRoot = projectRoot.storageRoot
|
||||||
val fileToPathConverter = RelativeFileToPathConverter(projectRoot)
|
val fileToPathConverter = RelativeFileToPathConverter(projectRoot)
|
||||||
val lookupStorage = LookupStorage(storageRoot, fileToPathConverter)
|
val lookupStorage = LookupStorage(
|
||||||
|
storageRoot,
|
||||||
|
fileToPathConverter,
|
||||||
|
storeFullFqNames = CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_CLASSPATH_SNAPSHOTS.value.toBooleanLenient() ?: false
|
||||||
|
)
|
||||||
val files = LinkedHashSet<String>()
|
val files = LinkedHashSet<String>()
|
||||||
val symbols = LinkedHashSet<LookupSymbol>()
|
val symbols = LinkedHashSet<LookupSymbol>()
|
||||||
val lookups = MultiMap.createOrderedSet<LookupSymbol, String>()
|
val lookups = MultiMap.createOrderedSet<LookupSymbol, String>()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ dependencies {
|
|||||||
api(project(":compiler:cli-js"))
|
api(project(":compiler:cli-js"))
|
||||||
api(project(":kotlin-build-common"))
|
api(project(":kotlin-build-common"))
|
||||||
api(project(":daemon-common"))
|
api(project(":daemon-common"))
|
||||||
|
implementation("com.google.code.gson:gson:${rootProject.extra["versions.jar.gson"]}")
|
||||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||||
|
|
||||||
testApi(commonDep("junit:junit"))
|
testApi(commonDep("junit:junit"))
|
||||||
|
|||||||
+9
-7
@@ -26,8 +26,8 @@ import java.io.File
|
|||||||
abstract class IncrementalCachesManager<PlatformCache : AbstractIncrementalCache<*>>(
|
abstract class IncrementalCachesManager<PlatformCache : AbstractIncrementalCache<*>>(
|
||||||
cachesRootDir: File,
|
cachesRootDir: File,
|
||||||
rootProjectDir: File?,
|
rootProjectDir: File?,
|
||||||
protected val reporter: ICReporter
|
protected val reporter: ICReporter,
|
||||||
) {
|
storeFullFqNamesInLookupCache: Boolean = false) {
|
||||||
val pathConverter = IncrementalFileToPathConverter(rootProjectDir)
|
val pathConverter = IncrementalFileToPathConverter(rootProjectDir)
|
||||||
private val caches = arrayListOf<BasicMapsOwner>()
|
private val caches = arrayListOf<BasicMapsOwner>()
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ abstract class IncrementalCachesManager<PlatformCache : AbstractIncrementalCache
|
|||||||
private val lookupCacheDir = File(cachesRootDir, "lookups").apply { mkdirs() }
|
private val lookupCacheDir = File(cachesRootDir, "lookups").apply { mkdirs() }
|
||||||
|
|
||||||
val inputsCache: InputsCache = InputsCache(inputSnapshotsCacheDir, reporter, pathConverter).apply { registerCache() }
|
val inputsCache: InputsCache = InputsCache(inputSnapshotsCacheDir, reporter, pathConverter).apply { registerCache() }
|
||||||
val lookupCache: LookupStorage = LookupStorage(lookupCacheDir, pathConverter).apply { registerCache() }
|
val lookupCache: LookupStorage = LookupStorage(lookupCacheDir, pathConverter, storeFullFqNamesInLookupCache).apply { registerCache() }
|
||||||
abstract val platformCache: PlatformCache
|
abstract val platformCache: PlatformCache
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -79,8 +79,9 @@ class IncrementalJvmCachesManager(
|
|||||||
cacheDirectory: File,
|
cacheDirectory: File,
|
||||||
rootProjectDir: File?,
|
rootProjectDir: File?,
|
||||||
outputDir: File,
|
outputDir: File,
|
||||||
reporter: ICReporter
|
reporter: ICReporter,
|
||||||
) : IncrementalCachesManager<IncrementalJvmCache>(cacheDirectory, rootProjectDir, reporter) {
|
storeFullFqNamesInLookupCache: Boolean = false
|
||||||
|
) : IncrementalCachesManager<IncrementalJvmCache>(cacheDirectory, rootProjectDir, reporter, storeFullFqNamesInLookupCache) {
|
||||||
private val jvmCacheDir = File(cacheDirectory, "jvm").apply { mkdirs() }
|
private val jvmCacheDir = File(cacheDirectory, "jvm").apply { mkdirs() }
|
||||||
override val platformCache = IncrementalJvmCache(jvmCacheDir, outputDir, pathConverter).apply { registerCache() }
|
override val platformCache = IncrementalJvmCache(jvmCacheDir, outputDir, pathConverter).apply { registerCache() }
|
||||||
}
|
}
|
||||||
@@ -89,8 +90,9 @@ class IncrementalJsCachesManager(
|
|||||||
cachesRootDir: File,
|
cachesRootDir: File,
|
||||||
rootProjectDir: File?,
|
rootProjectDir: File?,
|
||||||
reporter: ICReporter,
|
reporter: ICReporter,
|
||||||
serializerProtocol: SerializerExtensionProtocol
|
serializerProtocol: SerializerExtensionProtocol,
|
||||||
) : IncrementalCachesManager<IncrementalJsCache>(cachesRootDir, rootProjectDir, reporter) {
|
storeFullFqNamesInLookupCache: Boolean
|
||||||
|
) : IncrementalCachesManager<IncrementalJsCache>(cachesRootDir, rootProjectDir, reporter, storeFullFqNamesInLookupCache) {
|
||||||
private val jsCacheFile = File(cachesRootDir, "js").apply { mkdirs() }
|
private val jsCacheFile = File(cachesRootDir, "js").apply { mkdirs() }
|
||||||
override val platformCache = IncrementalJsCache(jsCacheFile, pathConverter, serializerProtocol).apply { registerCache() }
|
override val platformCache = IncrementalJsCache(jsCacheFile, pathConverter, serializerProtocol).apply { registerCache() }
|
||||||
}
|
}
|
||||||
+38
-32
@@ -19,9 +19,9 @@ package org.jetbrains.kotlin.incremental
|
|||||||
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
|
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
|
||||||
import org.jetbrains.kotlin.build.GeneratedFile
|
import org.jetbrains.kotlin.build.GeneratedFile
|
||||||
import org.jetbrains.kotlin.build.report.BuildReporter
|
import org.jetbrains.kotlin.build.report.BuildReporter
|
||||||
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
|
||||||
import org.jetbrains.kotlin.build.report.metrics.BuildAttribute
|
import org.jetbrains.kotlin.build.report.metrics.BuildAttribute
|
||||||
import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
|
import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
||||||
import org.jetbrains.kotlin.build.report.metrics.measure
|
import org.jetbrains.kotlin.build.report.metrics.measure
|
||||||
import org.jetbrains.kotlin.cli.common.*
|
import org.jetbrains.kotlin.cli.common.*
|
||||||
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||||
@@ -39,8 +39,6 @@ import org.jetbrains.kotlin.name.FqName
|
|||||||
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
|
|
||||||
abstract class IncrementalCompilerRunner<
|
abstract class IncrementalCompilerRunner<
|
||||||
Args : CommonCompilerArguments,
|
Args : CommonCompilerArguments,
|
||||||
@@ -108,7 +106,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
caches.close(false)
|
caches.close(false)
|
||||||
// todo: we can recompile all files incrementally (not cleaning caches), so rebuild won't propagate
|
// todo: we can recompile all files incrementally (not cleaning caches), so rebuild won't propagate
|
||||||
reporter.measure(BuildTime.CLEAR_OUTPUT_ON_REBUILD) {
|
reporter.measure(BuildTime.CLEAR_OUTPUT_ON_REBUILD) {
|
||||||
clearLocalStateOnRebuild(args)
|
clearOutputsOnRebuild(args)
|
||||||
}
|
}
|
||||||
caches = createCacheManager(args, projectDir)
|
caches = createCacheManager(args, projectDir)
|
||||||
if (providedChangedFiles == null) {
|
if (providedChangedFiles == null) {
|
||||||
@@ -134,7 +132,6 @@ abstract class IncrementalCompilerRunner<
|
|||||||
else -> providedChangedFiles
|
else -> providedChangedFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val compilationMode = sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot)
|
val compilationMode = sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot)
|
||||||
|
|
||||||
val exitCode = when (compilationMode) {
|
val exitCode = when (compilationMode) {
|
||||||
@@ -162,7 +159,8 @@ abstract class IncrementalCompilerRunner<
|
|||||||
allSourceFiles,
|
allSourceFiles,
|
||||||
compilationMode,
|
compilationMode,
|
||||||
messageCollector,
|
messageCollector,
|
||||||
withSnapshot)
|
withSnapshot
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CompilationMode.Rebuild -> {
|
is CompilationMode.Rebuild -> {
|
||||||
@@ -170,6 +168,8 @@ abstract class IncrementalCompilerRunner<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
performWorkAfterCompilation(caches)
|
||||||
|
|
||||||
if (!caches.close(flush = true)) throw RuntimeException("Could not flush caches")
|
if (!caches.close(flush = true)) throw RuntimeException("Could not flush caches")
|
||||||
// Here we should analyze exit code of compiler. E.g. compiler failure should lead to caches rebuild,
|
// Here we should analyze exit code of compiler. E.g. compiler failure should lead to caches rebuild,
|
||||||
// but now JsKlib compiler reports invalid exit code.
|
// but now JsKlib compiler reports invalid exit code.
|
||||||
@@ -182,7 +182,7 @@ abstract class IncrementalCompilerRunner<
|
|||||||
)
|
)
|
||||||
if (cacheDirectory.exists() && cacheDirectory.isDirectory()) {
|
if (cacheDirectory.exists() && cacheDirectory.isDirectory()) {
|
||||||
cacheDirectory.walkTopDown().filter { it.isFile }.map { it.length() }.sum().let {
|
cacheDirectory.walkTopDown().filter { it.isFile }.map { it.length() }.sum().let {
|
||||||
reporter.addMetric(BuildPerformanceMetric.OUTPUT_SIZE, it)
|
reporter.addMetric(BuildPerformanceMetric.CACHE_DIRECTORY_SIZE, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,37 +193,42 @@ abstract class IncrementalCompilerRunner<
|
|||||||
rebuild(BuildAttribute.CACHE_CORRUPTION)
|
rebuild(BuildAttribute.CACHE_CORRUPTION)
|
||||||
} finally {
|
} finally {
|
||||||
if (cachesMayBeCorrupted) {
|
if (cachesMayBeCorrupted) {
|
||||||
clearLocalStateOnRebuild(args)
|
clearOutputsOnRebuild(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearLocalStateOnRebuild(args: Args) {
|
/**
|
||||||
|
* Deletes output files and contents of output directories on rebuild (including `@LocalState` files/directories).
|
||||||
|
*
|
||||||
|
* If the output directories do not yet exist, they will be created.
|
||||||
|
*/
|
||||||
|
private fun clearOutputsOnRebuild(args: Args) {
|
||||||
val destinationDir = destinationDir(args)
|
val destinationDir = destinationDir(args)
|
||||||
|
val (outputDirsThatExist, regularOrNonExistentOutputFiles) = outputFiles.partition { it.isDirectory }
|
||||||
|
val regularOutputFiles = regularOrNonExistentOutputFiles.filter { it.exists() }
|
||||||
|
|
||||||
reporter.reportVerbose { "Clearing output on rebuild" }
|
// outputDirsThatExist may or may not contain destinationDir and workingDir.
|
||||||
for (file in sequenceOf(destinationDir, workingDir) + outputFiles.asSequence()) {
|
// Collect all of them so that we don't miss any output directories.
|
||||||
val deleted: Boolean? = when {
|
// Use Set to avoid duplication.
|
||||||
file.isDirectory -> {
|
val allOutputDirs = setOf(destinationDir, workingDir) + outputDirsThatExist
|
||||||
reporter.reportVerbose { " Deleting directory $file" }
|
|
||||||
file.deleteRecursively()
|
reporter.reportVerbose { "Clearing outputs on rebuild" }
|
||||||
}
|
allOutputDirs.forEach { dir ->
|
||||||
file.isFile -> {
|
reporter.reportVerbose { " Deleting contents of directory '${dir.path}'" }
|
||||||
reporter.reportVerbose { " Deleting $file" }
|
dir.listFiles()?.forEach {
|
||||||
file.delete()
|
it.deleteRecursively()
|
||||||
}
|
if (it.exists()) throw IOException("Could not delete '${it.path}'")
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deleted == false) {
|
dir.mkdirs()
|
||||||
reporter.reportVerbose { " Could not delete $file" }
|
if (!dir.exists()) throw IOException("Could not create directory '${dir.path}'")
|
||||||
}
|
}
|
||||||
|
regularOutputFiles.forEach { file ->
|
||||||
|
reporter.reportVerbose { " Deleting file '${file.path}'" }
|
||||||
|
file.delete()
|
||||||
|
if (file.exists()) throw IOException("Could not delete file '${file.path}'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destinationDir.exists()) throw IOException("Could not delete directory $destinationDir.")
|
|
||||||
if (workingDir.exists()) throw IOException("Could not delete internal caches in folder $workingDir")
|
|
||||||
destinationDir.mkdirs()
|
|
||||||
workingDir.mkdirs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sourcesToCompile(
|
private fun sourcesToCompile(
|
||||||
@@ -393,9 +398,8 @@ abstract class IncrementalCompilerRunner<
|
|||||||
caches.platformCache.updateComplementaryFiles(dirtySources, expectActualTracker)
|
caches.platformCache.updateComplementaryFiles(dirtySources, expectActualTracker)
|
||||||
caches.inputsCache.registerOutputForSourceFiles(generatedFiles)
|
caches.inputsCache.registerOutputForSourceFiles(generatedFiles)
|
||||||
caches.lookupCache.update(lookupTracker, sourcesToCompile, removedKotlinSources)
|
caches.lookupCache.update(lookupTracker, sourcesToCompile, removedKotlinSources)
|
||||||
updateCaches(services, caches, generatedFiles, changesCollector)
|
updateCaches(services, caches, generatedFiles, changesCollector)
|
||||||
|
}
|
||||||
}
|
|
||||||
if (compilationMode is CompilationMode.Rebuild) {
|
if (compilationMode is CompilationMode.Rebuild) {
|
||||||
if (withSnapshot) {
|
if (withSnapshot) {
|
||||||
abiSnapshot.protos.putAll(changesCollector.protoDataChanges())
|
abiSnapshot.protos.putAll(changesCollector.protoDataChanges())
|
||||||
@@ -498,6 +502,8 @@ abstract class IncrementalCompilerRunner<
|
|||||||
BuildDiffsStorage.writeToFile(buildHistoryFile, BuildDiffsStorage(prevDiffs + newDiff), reporter)
|
BuildDiffsStorage.writeToFile(buildHistoryFile, BuildDiffsStorage(prevDiffs + newDiff), reporter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun performWorkAfterCompilation(caches: CacheManager) {}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DIRTY_SOURCES_FILE_NAME = "dirty-sources.txt"
|
const val DIRTY_SOURCES_FILE_NAME = "dirty-sources.txt"
|
||||||
const val LAST_BUILD_INFO_FILE_NAME = "last-build.bin"
|
const val LAST_BUILD_INFO_FILE_NAME = "last-build.bin"
|
||||||
|
|||||||
+8
-3
@@ -20,9 +20,8 @@ import org.jetbrains.kotlin.build.GeneratedFile
|
|||||||
import org.jetbrains.kotlin.build.report.BuildReporter
|
import org.jetbrains.kotlin.build.report.BuildReporter
|
||||||
import org.jetbrains.kotlin.build.report.ICReporter
|
import org.jetbrains.kotlin.build.report.ICReporter
|
||||||
import org.jetbrains.kotlin.build.report.metrics.BuildAttribute
|
import org.jetbrains.kotlin.build.report.metrics.BuildAttribute
|
||||||
import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
|
|
||||||
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
||||||
import org.jetbrains.kotlin.cli.common.*
|
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||||
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
|
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
|
||||||
import org.jetbrains.kotlin.cli.common.arguments.isIrBackendEnabled
|
import org.jetbrains.kotlin.cli.common.arguments.isIrBackendEnabled
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
@@ -92,7 +91,13 @@ class IncrementalJsCompilerRunner(
|
|||||||
|
|
||||||
override fun createCacheManager(args: K2JSCompilerArguments, projectDir: File?): IncrementalJsCachesManager {
|
override fun createCacheManager(args: K2JSCompilerArguments, projectDir: File?): IncrementalJsCachesManager {
|
||||||
val serializerProtocol = if (!args.isIrBackendEnabled()) JsSerializerProtocol else KlibMetadataSerializerProtocol
|
val serializerProtocol = if (!args.isIrBackendEnabled()) JsSerializerProtocol else KlibMetadataSerializerProtocol
|
||||||
return IncrementalJsCachesManager(cacheDirectory, projectDir, reporter, serializerProtocol)
|
return IncrementalJsCachesManager(
|
||||||
|
cacheDirectory,
|
||||||
|
projectDir,
|
||||||
|
reporter,
|
||||||
|
serializerProtocol,
|
||||||
|
storeFullFqNamesInLookupCache = withSnapshot
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun destinationDir(args: K2JSCompilerArguments): File {
|
override fun destinationDir(args: K2JSCompilerArguments): File {
|
||||||
|
|||||||
+75
-24
@@ -41,17 +41,18 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
|||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||||
import org.jetbrains.kotlin.config.Services
|
import org.jetbrains.kotlin.config.Services
|
||||||
|
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotDisabled
|
||||||
|
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.*
|
||||||
|
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailableForJSCompiler
|
||||||
|
import org.jetbrains.kotlin.incremental.classpathDiff.*
|
||||||
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
|
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
|
||||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||||
import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory
|
import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory
|
||||||
import org.jetbrains.kotlin.incremental.multiproject.ModulesApiHistory
|
import org.jetbrains.kotlin.incremental.multiproject.ModulesApiHistory
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.ListExternalizer
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.saveToFile
|
||||||
import org.jetbrains.kotlin.incremental.util.BufferingMessageCollector
|
import org.jetbrains.kotlin.incremental.util.BufferingMessageCollector
|
||||||
import org.jetbrains.kotlin.incremental.util.Either
|
import org.jetbrains.kotlin.incremental.util.Either
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailable.UnableToCompute
|
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailable.ForJSCompiler
|
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailable.ReservedForTestsOnly
|
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailable.ForNonIncrementalRun
|
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailable.ClasspathSnapshotIsDisabled
|
|
||||||
import org.jetbrains.kotlin.load.java.JavaClassesTracker
|
import org.jetbrains.kotlin.load.java.JavaClassesTracker
|
||||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
||||||
@@ -88,7 +89,7 @@ fun makeIncrementally(
|
|||||||
buildHistoryFile = buildHistoryFile,
|
buildHistoryFile = buildHistoryFile,
|
||||||
modulesApiHistory = EmptyModulesApiHistory,
|
modulesApiHistory = EmptyModulesApiHistory,
|
||||||
kotlinSourceFilesExtensions = kotlinExtensions,
|
kotlinSourceFilesExtensions = kotlinExtensions,
|
||||||
classpathChanges = ReservedForTestsOnly
|
classpathChanges = ClasspathSnapshotDisabled
|
||||||
)
|
)
|
||||||
//TODO set properly
|
//TODO set properly
|
||||||
compiler.compile(sourceFiles, args, messageCollector, providedChangedFiles = null)
|
compiler.compile(sourceFiles, args, messageCollector, providedChangedFiles = null)
|
||||||
@@ -123,7 +124,7 @@ class IncrementalJvmCompilerRunner(
|
|||||||
outputFiles: Collection<File>,
|
outputFiles: Collection<File>,
|
||||||
private val modulesApiHistory: ModulesApiHistory,
|
private val modulesApiHistory: ModulesApiHistory,
|
||||||
override val kotlinSourceFilesExtensions: List<String> = DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS,
|
override val kotlinSourceFilesExtensions: List<String> = DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS,
|
||||||
private val classpathChanges: ClasspathChanges,
|
private val classpathChanges: ClasspathChanges
|
||||||
) : IncrementalCompilerRunner<K2JVMCompilerArguments, IncrementalJvmCachesManager>(
|
) : IncrementalCompilerRunner<K2JVMCompilerArguments, IncrementalJvmCachesManager>(
|
||||||
workingDir,
|
workingDir,
|
||||||
"caches-jvm",
|
"caches-jvm",
|
||||||
@@ -135,7 +136,13 @@ class IncrementalJvmCompilerRunner(
|
|||||||
IncrementalCompilation.isEnabledForJvm()
|
IncrementalCompilation.isEnabledForJvm()
|
||||||
|
|
||||||
override fun createCacheManager(args: K2JVMCompilerArguments, projectDir: File?): IncrementalJvmCachesManager =
|
override fun createCacheManager(args: K2JVMCompilerArguments, projectDir: File?): IncrementalJvmCachesManager =
|
||||||
IncrementalJvmCachesManager(cacheDirectory, projectDir, File(args.destination), reporter)
|
IncrementalJvmCachesManager(
|
||||||
|
cacheDirectory,
|
||||||
|
projectDir,
|
||||||
|
File(args.destination),
|
||||||
|
reporter,
|
||||||
|
storeFullFqNamesInLookupCache = withSnapshot || classpathChanges is ClasspathChanges.ClasspathSnapshotEnabled
|
||||||
|
)
|
||||||
|
|
||||||
override fun destinationDir(args: K2JVMCompilerArguments): File =
|
override fun destinationDir(args: K2JVMCompilerArguments): File =
|
||||||
args.destinationAsFile
|
args.destinationAsFile
|
||||||
@@ -203,6 +210,10 @@ class IncrementalJvmCompilerRunner(
|
|||||||
return abiSnapshots
|
return abiSnapshots
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by `calculateSourcesToCompileImpl` and `performWorkAfterCompilation` methods below.
|
||||||
|
// Thread safety: There is no concurrent access to this variable.
|
||||||
|
private var currentClasspathSnapshot: ClasspathSnapshot? = null
|
||||||
|
|
||||||
private fun calculateSourcesToCompileImpl(
|
private fun calculateSourcesToCompileImpl(
|
||||||
caches: IncrementalJvmCachesManager,
|
caches: IncrementalJvmCachesManager,
|
||||||
changedFiles: ChangedFiles.Known,
|
changedFiles: ChangedFiles.Known,
|
||||||
@@ -212,26 +223,34 @@ class IncrementalJvmCompilerRunner(
|
|||||||
val dirtyFiles = DirtyFilesContainer(caches, reporter, kotlinSourceFilesExtensions)
|
val dirtyFiles = DirtyFilesContainer(caches, reporter, kotlinSourceFilesExtensions)
|
||||||
initDirtyFiles(dirtyFiles, changedFiles)
|
initDirtyFiles(dirtyFiles, changedFiles)
|
||||||
|
|
||||||
val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) ?: return CompilationMode.Rebuild(BuildAttribute.NO_BUILD_HISTORY)
|
|
||||||
reporter.reportVerbose { "Last Kotlin Build info -- $lastBuildInfo" }
|
|
||||||
|
|
||||||
val classpathChanges = when (classpathChanges) {
|
val classpathChanges = when (classpathChanges) {
|
||||||
// Note: classpathChanges is deserialized, so they are no longer singleton objects and need to be compared using `is` (not `==`)
|
// Note: classpathChanges is deserialized, so they are no longer singleton objects and need to be compared using `is` (not `==`)
|
||||||
is ClasspathChanges.Available -> ChangesEither.Known(classpathChanges.lookupSymbols, classpathChanges.fqNames)
|
is Empty -> ChangesEither.Known(emptySet(), emptySet())
|
||||||
is ClasspathChanges.NotAvailable -> when (classpathChanges) {
|
is ToBeComputedByIncrementalCompiler -> reporter.measure(BuildTime.COMPUTE_CLASSPATH_CHANGES) {
|
||||||
is UnableToCompute, is ClasspathSnapshotIsDisabled, is ReservedForTestsOnly -> {
|
check(currentClasspathSnapshot == null)
|
||||||
reporter.measure(BuildTime.IC_ANALYZE_CHANGES_IN_DEPENDENCIES) {
|
currentClasspathSnapshot = reporter.measure(BuildTime.LOAD_CURRENT_CLASSPATH_SNAPSHOT) {
|
||||||
val scopes = caches.lookupCache.lookupMap.keys.map { it.scope.ifBlank { it.name } }.distinct()
|
CachedClasspathSnapshotSerializer.load(classpathChanges.classpathSnapshotFiles.currentClasspathEntrySnapshotFiles)
|
||||||
getClasspathChanges(
|
|
||||||
args.classpathAsList, changedFiles, lastBuildInfo, modulesApiHistory, reporter, abiSnapshots, withSnapshot,
|
|
||||||
caches.platformCache, scopes
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ForNonIncrementalRun, is ForJSCompiler -> {
|
|
||||||
error("Unexpected type for this code path: ${classpathChanges.javaClass.name}.")
|
|
||||||
}
|
}
|
||||||
|
ClasspathChangesComputer.computeChangedAndImpactedSet(
|
||||||
|
currentClasspathSnapshot!!,
|
||||||
|
caches.lookupCache,
|
||||||
|
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile,
|
||||||
|
reporter
|
||||||
|
).getChanges()
|
||||||
}
|
}
|
||||||
|
is NotAvailableDueToMissingClasspathSnapshot -> ChangesEither.Unknown(BuildAttribute.CLASSPATH_SNAPSHOT_NOT_FOUND)
|
||||||
|
is NotAvailableForNonIncrementalRun -> ChangesEither.Unknown(BuildAttribute.UNKNOWN_CHANGES_IN_GRADLE_INPUTS)
|
||||||
|
is ClasspathSnapshotDisabled -> reporter.measure(BuildTime.IC_ANALYZE_CHANGES_IN_DEPENDENCIES) {
|
||||||
|
val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) ?: return CompilationMode.Rebuild(BuildAttribute.IC_IS_NOT_ENABLED)
|
||||||
|
reporter.reportVerbose { "Last Kotlin Build info -- $lastBuildInfo" }
|
||||||
|
val scopes = caches.lookupCache.lookupMap.keys.map { it.scope.ifBlank { it.name } }.distinct()
|
||||||
|
|
||||||
|
getClasspathChanges(
|
||||||
|
args.classpathAsList, changedFiles, lastBuildInfo, modulesApiHistory, reporter, abiSnapshots, withSnapshot,
|
||||||
|
caches.platformCache, scopes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is NotAvailableForJSCompiler -> error("Unexpected type for this code path: ${classpathChanges.javaClass.name}.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_VARIABLE") // for sealed when
|
@Suppress("UNUSED_VARIABLE") // for sealed when
|
||||||
@@ -434,6 +453,38 @@ class IncrementalJvmCompilerRunner(
|
|||||||
reportPerformanceData(compiler.defaultPerformanceManager)
|
reportPerformanceData(compiler.defaultPerformanceManager)
|
||||||
return exitCode
|
return exitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun performWorkAfterCompilation(caches: IncrementalJvmCachesManager) {
|
||||||
|
if (classpathChanges is ClasspathChanges.ClasspathSnapshotEnabled) {
|
||||||
|
reporter.measure(BuildTime.SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
||||||
|
shrinkAndSaveClasspathSnapshot(classpathChanges, caches.lookupCache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shrinkAndSaveClasspathSnapshot(classpathChanges: ClasspathChanges.ClasspathSnapshotEnabled, lookupStorage: LookupStorage) {
|
||||||
|
val classpathSnapshot = currentClasspathSnapshot ?: reporter.measure(BuildTime.LOAD_CLASSPATH_SNAPSHOT) {
|
||||||
|
CachedClasspathSnapshotSerializer.load(classpathChanges.classpathSnapshotFiles.currentClasspathEntrySnapshotFiles)
|
||||||
|
}
|
||||||
|
val shrunkClasspathSnapshot = reporter.measure(BuildTime.SHRINK_CLASSPATH_SNAPSHOT) {
|
||||||
|
ClasspathSnapshotShrinker.shrink(classpathSnapshot, lookupStorage, reporter)
|
||||||
|
}
|
||||||
|
reporter.measure(BuildTime.SAVE_SHRUNK_CLASSPATH_SNAPSHOT) {
|
||||||
|
ListExternalizer(ClassSnapshotWithHashExternalizer).saveToFile(
|
||||||
|
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile,
|
||||||
|
shrunkClasspathSnapshot
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reporter.addMetric(
|
||||||
|
BuildPerformanceMetric.ORIGINAL_CLASSPATH_SNAPSHOT_SIZE,
|
||||||
|
classpathChanges.classpathSnapshotFiles.currentClasspathEntrySnapshotFiles.sumOf { it.length() }
|
||||||
|
)
|
||||||
|
reporter.addMetric(
|
||||||
|
BuildPerformanceMetric.SHRUNK_CLASSPATH_SNAPSHOT_SIZE,
|
||||||
|
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile.length()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var K2JVMCompilerArguments.destinationAsFile: File
|
var K2JVMCompilerArguments.destinationAsFile: File
|
||||||
|
|||||||
+4
-4
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
import org.jetbrains.kotlin.incremental.ChangesEither
|
||||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
|
||||||
/** Intermediate data to compute [ClasspathChanges] (see [toClasspathChanges]). */
|
/** Set of classes, class members, and top-level members that are changed (or impacted by a change). */
|
||||||
class ChangeSet(
|
class ChangeSet(
|
||||||
|
|
||||||
/** Set of changed classes, preferably ordered by not required. */
|
/** Set of changed classes, preferably ordered by not required. */
|
||||||
@@ -73,7 +73,7 @@ class ChangeSet(
|
|||||||
changedTopLevelMembers + other.changedTopLevelMembers
|
changedTopLevelMembers + other.changedTopLevelMembers
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toClasspathChanges(): ClasspathChanges.Available {
|
internal fun getChanges(): ChangesEither.Known {
|
||||||
val lookupSymbols = mutableSetOf<LookupSymbol>()
|
val lookupSymbols = mutableSetOf<LookupSymbol>()
|
||||||
val fqNames = mutableSetOf<FqName>()
|
val fqNames = mutableSetOf<FqName>()
|
||||||
|
|
||||||
@@ -98,6 +98,6 @@ class ChangeSet(
|
|||||||
fqNames.add(changedPackage)
|
fqNames.add(changedPackage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClasspathChanges.Available(lookupSymbols, fqNames)
|
return ChangesEither.Known(lookupSymbols, fqNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+161
-199
@@ -6,9 +6,14 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ImpactAnalysis.computeImpactedSet
|
import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.measure
|
||||||
import org.jetbrains.kotlin.incremental.*
|
import org.jetbrains.kotlin.incremental.*
|
||||||
|
import org.jetbrains.kotlin.incremental.classpathDiff.ImpactAnalysis.computeImpactedSet
|
||||||
import org.jetbrains.kotlin.incremental.storage.FileToCanonicalPathConverter
|
import org.jetbrains.kotlin.incremental.storage.FileToCanonicalPathConverter
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.ListExternalizer
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.loadFromFile
|
||||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||||
import org.jetbrains.kotlin.metadata.deserialization.supertypes
|
import org.jetbrains.kotlin.metadata.deserialization.supertypes
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
@@ -18,156 +23,101 @@ import org.jetbrains.kotlin.serialization.deserialization.getClassId
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/** Computes [ClasspathChanges] between two [ClasspathSnapshot]s .*/
|
/** Computes changes between two [ClasspathSnapshot]s .*/
|
||||||
object ClasspathChangesComputer {
|
object ClasspathChangesComputer {
|
||||||
|
|
||||||
fun compute(
|
/**
|
||||||
currentClasspathEntrySnapshotFiles: List<File>,
|
* Computes changes between the current and previous [ClasspathSnapshot]s, plus unchanged elements that are impacted by the changes.
|
||||||
previousClasspathEntrySnapshotFiles: List<File>,
|
*
|
||||||
unchangedCurrentClasspathEntrySnapshotFiles: List<File>
|
* NOTE: The original classpath may contain duplicate classes, but the shrunk classpath must not contain duplicate classes.
|
||||||
): ClasspathChanges {
|
*/
|
||||||
// To improve performance, we will compute changes for the changed snapshot files only, ignoring unchanged ones. (Duplicate classes
|
fun computeChangedAndImpactedSet(
|
||||||
// will make this a bit tricky, but it will be dealt with below.)
|
currentClasspathSnapshot: ClasspathSnapshot,
|
||||||
// First, align unchanged snapshot files in the current classpath with unchanged snapshot files in the previous classpath. Gradle
|
lookupStorageInPreviousRun: LookupStorage,
|
||||||
// has this information, but doesn't expose it, so we have to reconstruct it here.
|
shrunkPreviousClasspathSnapshotFile: File,
|
||||||
val unchangedCurrentToPreviousAlignment: Map<File, File> =
|
metrics: BuildMetricsReporter
|
||||||
alignUnchangedSnapshotFiles(unchangedCurrentClasspathEntrySnapshotFiles, previousClasspathEntrySnapshotFiles)
|
): ChangeSet {
|
||||||
|
val shrunkCurrentClasspathSnapshot = metrics.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT) {
|
||||||
// Use sets to make presence checks faster
|
ClasspathSnapshotShrinker.shrink(currentClasspathSnapshot, lookupStorageInPreviousRun, metrics)
|
||||||
val unchangedCurrentFiles: Set<File> = unchangedCurrentToPreviousAlignment.keys
|
}
|
||||||
val unchangedPreviousFiles: Set<File> = unchangedCurrentToPreviousAlignment.values.toSet()
|
val shrunkPreviousClasspathSnapshot = metrics.measure(BuildTime.LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT) {
|
||||||
|
ListExternalizer(ClassSnapshotWithHashExternalizer).loadFromFile(shrunkPreviousClasspathSnapshotFile)
|
||||||
// We will split the current files into 2 groups:
|
}
|
||||||
// 1a) Unchanged current files
|
return metrics.measure(BuildTime.COMPUTE_CHANGED_AND_IMPACTED_SET) {
|
||||||
// 1b) Added files
|
computeChangedAndImpactedSet(shrunkCurrentClasspathSnapshot, shrunkPreviousClasspathSnapshot, metrics)
|
||||||
// We will split the previous files into 2 groups:
|
|
||||||
// 2a) Unchanged previous files
|
|
||||||
// 2b) Removed files
|
|
||||||
// If the classpath doesn't contain duplicate classes, comparing (1b) with (2b) would be enough.
|
|
||||||
// However, if the classpath contains duplicate classes, comparing (1b) with (2b) would not be enough.
|
|
||||||
// Therefore, to deal with duplicate classes while still being able to compare (1b) with (2b), we will find snapshot files in groups
|
|
||||||
// (1a) and (2a) that have duplicate classes with groups (1b) or (2b) and add them to groups (1b) and (2b). Duplicate classes in
|
|
||||||
// groups (1b) and (2b) will then be handled in a separate step (see ClasspathChangesComputer.getNonDuplicateClassSnapshots).
|
|
||||||
val addedFiles: List<File> = currentClasspathEntrySnapshotFiles.filter { it !in unchangedCurrentFiles }
|
|
||||||
val removedFiles: List<File> = previousClasspathEntrySnapshotFiles.filter { it !in unchangedPreviousFiles }
|
|
||||||
|
|
||||||
val adjustedAddedFiles = addedFiles.toMutableSet()
|
|
||||||
val adjustedRemovedFiles = removedFiles.toMutableSet()
|
|
||||||
unchangedCurrentToPreviousAlignment.forEach { (unchangedCurrentFile, unchangedPreviousFile) ->
|
|
||||||
if (unchangedCurrentFile.containsDuplicatesWith(addedFiles) || unchangedPreviousFile.containsDuplicatesWith(removedFiles)) {
|
|
||||||
adjustedAddedFiles.add(unchangedCurrentFile)
|
|
||||||
adjustedRemovedFiles.add(unchangedPreviousFile)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the original order of added/removed files as it is important for the handling of duplicate classes.
|
|
||||||
val finalAddedFiles: List<File> = currentClasspathEntrySnapshotFiles.filter { it in adjustedAddedFiles }
|
|
||||||
val finalRemovedFiles: List<File> = previousClasspathEntrySnapshotFiles.filter { it in adjustedRemovedFiles }
|
|
||||||
|
|
||||||
val changedCurrentSnapshot = ClasspathSnapshotSerializer.load(finalAddedFiles)
|
|
||||||
val changedPreviousSnapshot = ClasspathSnapshotSerializer.load(finalRemovedFiles)
|
|
||||||
|
|
||||||
return compute(changedCurrentSnapshot, changedPreviousSnapshot)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the unchanged snapshot files of the current build to the unchanged snapshot files of the previous build (selected from all the
|
* Computes changes between the current and previous lists of classes, plus unchanged elements that are impacted by the changes.
|
||||||
* snapshot files of the previous build).
|
|
||||||
*
|
*
|
||||||
* Note that the unchanged files of the current build were detected by Gradle, so a mapping must exist for each of them, we only have to
|
* NOTE: Each list of classes must not contain duplicates.
|
||||||
* find it.
|
|
||||||
*
|
|
||||||
* IMPORTANT: The alignment algorithm must use the same input normalization that is used for snapshot files in the Gradle task.
|
|
||||||
* Currently, snapshot files are annotated with `@Classpath` and are regular files (not jars), so (only) their contents and order
|
|
||||||
* matter.
|
|
||||||
*/
|
*/
|
||||||
private fun alignUnchangedSnapshotFiles(unchangedCurrentSnapshotFiles: List<File>, previousSnapshotFiles: List<File>): Map<File, File> {
|
fun computeChangedAndImpactedSet(
|
||||||
val sizeToPreviousFiles: Map<Long, List<IndexedValue<File>>> = previousSnapshotFiles.withIndex().groupBy { it.value.length() }
|
currentClassSnapshots: List<ClassSnapshotWithHash>,
|
||||||
|
previousClassSnapshots: List<ClassSnapshotWithHash>,
|
||||||
|
metrics: BuildMetricsReporter
|
||||||
|
): ChangeSet {
|
||||||
|
val currentClasses: Map<ClassId, ClassSnapshotWithHash> = currentClassSnapshots.associateBy { it.classSnapshot.getClassId() }
|
||||||
|
val previousClasses: Map<ClassId, ClassSnapshotWithHash> = previousClassSnapshots.associateBy { it.classSnapshot.getClassId() }
|
||||||
|
|
||||||
var startIndexToSearch = 0
|
val changedCurrentClasses: List<ClassSnapshot> = currentClasses.filter { (classId, currentClass) ->
|
||||||
return unchangedCurrentSnapshotFiles.associateWith { unchangedCurrentFile ->
|
val previousClass = previousClasses[classId]
|
||||||
val candidates = (sizeToPreviousFiles[unchangedCurrentFile.length()] ?: emptyList()).filter { it.index >= startIndexToSearch }
|
previousClass == null || currentClass.hash != previousClass.hash
|
||||||
val unchangedPreviousFileWithIndex: IndexedValue<File> = if (candidates.size == 1) {
|
}.map { it.value.classSnapshot }
|
||||||
// A matching file must exist, so if there is only one candidate, it is the one.
|
|
||||||
candidates.single()
|
val changedPreviousClasses: List<ClassSnapshot> = previousClasses.filter { (classId, previousClass) ->
|
||||||
} else {
|
val currentClass = currentClasses[classId]
|
||||||
// If there are multiple matching files, select the first one. (Even if it doesn't match Gradle's alignment, it is still a
|
currentClass == null || currentClass.hash != previousClass.hash
|
||||||
// correct alignment.)
|
}.map { it.value.classSnapshot }
|
||||||
val unchangedContents = unchangedCurrentFile.readBytes()
|
|
||||||
candidates.firstOrNull { candidate ->
|
val classChanges = metrics.measure(BuildTime.COMPUTE_CLASS_CHANGES) {
|
||||||
unchangedContents.contentEquals(candidate.value.readBytes())
|
computeClassChanges(changedCurrentClasses, changedPreviousClasses, metrics)
|
||||||
} ?: error("Can't find previous snapshot file of unchanged current snapshot file '${unchangedCurrentFile.path}'")
|
|
||||||
}
|
|
||||||
startIndexToSearch = unchangedPreviousFileWithIndex.index + 1
|
|
||||||
unchangedPreviousFileWithIndex.value
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns `true` if this snapshot file contains a duplicate class with another snapshot file in the given list. */
|
if (classChanges.isEmpty()) {
|
||||||
@Suppress("unused", "UNUSED_PARAMETER")
|
return classChanges
|
||||||
private fun File.containsDuplicatesWith(otherSnapshotFiles: List<File>): Boolean {
|
}
|
||||||
// FIXME: Implement and optimize this method
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compute(currentClasspathSnapshot: ClasspathSnapshot, previousClasspathSnapshot: ClasspathSnapshot): ClasspathChanges {
|
return metrics.measure(BuildTime.COMPUTE_IMPACTED_SET) {
|
||||||
val currentClassSnapshots = currentClasspathSnapshot.getNonDuplicateClassSnapshots()
|
computeImpactedSet(classChanges, previousClasses.map { it.value.classSnapshot })
|
||||||
val previousClassSnapshots = previousClasspathSnapshot.getNonDuplicateClassSnapshots()
|
}
|
||||||
|
|
||||||
return computeClassChanges(currentClassSnapshots, previousClassSnapshots)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all [ClassSnapshot]s in this [ClasspathSnapshot].
|
* Computes changes between the current and previous lists of classes. The returned result does not need to include elements that are
|
||||||
|
* impacted by the changes.
|
||||||
*
|
*
|
||||||
* If there are duplicate classes on the classpath, retain only the first one to match the compiler's behavior.
|
* NOTE: Each list of classes must not contain duplicates.
|
||||||
*/
|
*/
|
||||||
private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshot> {
|
fun computeClassChanges(
|
||||||
val classSnapshots = LinkedHashMap<String, ClassSnapshot>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
currentClassSnapshots: List<ClassSnapshot>,
|
||||||
for (classpathEntrySnapshot in classpathEntrySnapshots) {
|
previousClassSnapshots: List<ClassSnapshot>,
|
||||||
for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) {
|
metrics: BuildMetricsReporter
|
||||||
classSnapshots.putIfAbsent(unixStyleRelativePath, classSnapshot)
|
): ChangeSet {
|
||||||
|
val asmBasedSnapshotPredicate: (ClassSnapshot) -> Boolean = {
|
||||||
|
when (it) {
|
||||||
|
is RegularJavaClassSnapshot -> true
|
||||||
|
is KotlinClassSnapshot, is ProtoBasedJavaClassSnapshot -> false
|
||||||
|
else -> error("Unexpected type (it should have been handled earlier): ${it.javaClass.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return classSnapshots.values.toList()
|
val (currentAsmBasedSnapshots, currentProtoBasedSnapshots) = currentClassSnapshots.partition(asmBasedSnapshotPredicate)
|
||||||
}
|
val (previousAsmBasedSnapshots, previousProtoBasedSnapshots) = previousClassSnapshots.partition(asmBasedSnapshotPredicate)
|
||||||
|
|
||||||
/**
|
val kotlinClassChanges = metrics.measure(BuildTime.COMPUTE_KOTLIN_CLASS_CHANGES) {
|
||||||
* Computes changes between two lists of [ClassSnapshot]s.
|
computeChangesForProtoBasedSnapshots(currentProtoBasedSnapshots, previousProtoBasedSnapshots)
|
||||||
*
|
|
||||||
* Each list must not contain duplicate classes.
|
|
||||||
*/
|
|
||||||
fun computeClassChanges(currentClassSnapshots: List<ClassSnapshot>, previousClassSnapshots: List<ClassSnapshot>): ClasspathChanges {
|
|
||||||
if (currentClassSnapshots.any { it is ContentHashJavaClassSnapshot }
|
|
||||||
|| previousClassSnapshots.any { it is ContentHashJavaClassSnapshot }) {
|
|
||||||
return ClasspathChanges.NotAvailable.UnableToCompute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore `EmptyJavaClassSnapshot`s as they don't impact the result
|
|
||||||
val currentNonEmptyClassSnapshots = currentClassSnapshots.filter { it !is EmptyJavaClassSnapshot }
|
|
||||||
val previousNonEmptyClassSnapshots = previousClassSnapshots.filter { it !is EmptyJavaClassSnapshot }
|
|
||||||
|
|
||||||
val (currentAsmBasedSnapshots, currentProtoBasedSnapshots) =
|
|
||||||
currentNonEmptyClassSnapshots.partition { it is RegularJavaClassSnapshot }
|
|
||||||
val (previousAsmBasedSnapshots, previousProtoBasedSnapshots) =
|
|
||||||
previousNonEmptyClassSnapshots.partition { it is RegularJavaClassSnapshot }
|
|
||||||
|
|
||||||
val changeSet1 = computeChangesForProtoBasedSnapshots(currentProtoBasedSnapshots, previousProtoBasedSnapshots)
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val changeSet2 = JavaClassChangesComputer.compute(
|
val javaClassChanges = metrics.measure(BuildTime.COMPUTE_JAVA_CLASS_CHANGES) {
|
||||||
currentAsmBasedSnapshots as List<RegularJavaClassSnapshot>,
|
JavaClassChangesComputer.compute(
|
||||||
previousAsmBasedSnapshots as List<RegularJavaClassSnapshot>
|
currentAsmBasedSnapshots as List<RegularJavaClassSnapshot>,
|
||||||
)
|
previousAsmBasedSnapshots as List<RegularJavaClassSnapshot>
|
||||||
|
)
|
||||||
val allChanges = changeSet1 + changeSet2
|
|
||||||
if (allChanges.isEmpty()) {
|
|
||||||
return allChanges.toClasspathChanges()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val impactedSet = computeImpactedSet(allChanges, previousNonEmptyClassSnapshots)
|
return kotlinClassChanges + javaClassChanges
|
||||||
|
|
||||||
return impactedSet.toClasspathChanges()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeChangesForProtoBasedSnapshots(
|
private fun computeChangesForProtoBasedSnapshots(
|
||||||
@@ -203,7 +153,7 @@ object ClasspathChangesComputer {
|
|||||||
)
|
)
|
||||||
incrementalJvmCache.markDirty(JvmClassName.byClassId(previousSnapshot.serializedJavaClass.classId))
|
incrementalJvmCache.markDirty(JvmClassName.byClassId(previousSnapshot.serializedJavaClass.classId))
|
||||||
}
|
}
|
||||||
is RegularJavaClassSnapshot, is ContentHashJavaClassSnapshot, is EmptyJavaClassSnapshot -> {
|
is RegularJavaClassSnapshot, is EmptyJavaClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
||||||
error("Unexpected type (it should have been handled earlier): ${previousSnapshot.javaClass.name}")
|
error("Unexpected type (it should have been handled earlier): ${previousSnapshot.javaClass.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,7 +183,7 @@ object ClasspathChangesComputer {
|
|||||||
collector = changesCollector
|
collector = changesCollector
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is RegularJavaClassSnapshot, is ContentHashJavaClassSnapshot, is EmptyJavaClassSnapshot -> {
|
is RegularJavaClassSnapshot, is EmptyJavaClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
||||||
error("Unexpected type (it should have been handled earlier): ${currentSnapshot.javaClass.name}")
|
error("Unexpected type (it should have been handled earlier): ${currentSnapshot.javaClass.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +205,7 @@ object ClasspathChangesComputer {
|
|||||||
|
|
||||||
private fun DirtyData.normalize(currentClassSnapshots: List<ClassSnapshot>, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
private fun DirtyData.normalize(currentClassSnapshots: List<ClassSnapshot>, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
||||||
val allClassIds = currentClassSnapshots.map { it.getClassId() }.toSet() + previousClassSnapshots.map { it.getClassId() }
|
val allClassIds = currentClassSnapshots.map { it.getClassId() }.toSet() + previousClassSnapshots.map { it.getClassId() }
|
||||||
val fqNameToClassId = LinkedHashMap<FqName, ClassId>(allClassIds.size)
|
val fqNameToClassId = HashMap<FqName, ClassId>(allClassIds.size)
|
||||||
allClassIds.forEach { classId ->
|
allClassIds.forEach { classId ->
|
||||||
val fqName = classId.asSingleFqName()
|
val fqName = classId.asSingleFqName()
|
||||||
check(!fqNameToClassId.contains(fqName)) {
|
check(!fqNameToClassId.contains(fqName)) {
|
||||||
@@ -264,68 +214,70 @@ object ClasspathChangesComputer {
|
|||||||
fqNameToClassId[fqName] = classId
|
fqNameToClassId[fqName] = classId
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChangeSet.Collector().run {
|
val changes = ChangeSet.Collector().run {
|
||||||
dirtyLookupSymbols.forEach {
|
dirtyLookupSymbols.forEach {
|
||||||
fqNameToClassId[FqName(it.scope)]?.let { classIdOfScope ->
|
val lookupSymbolFqName = if (it.scope.isEmpty()) FqName(it.name) else FqName("${it.scope}.${it.name}")
|
||||||
// If scope is a class, lookup symbol is a class member and maybe inner class
|
val lookupSymbolClassId: ClassId? = fqNameToClassId[lookupSymbolFqName]
|
||||||
fqNameToClassId[FqName("${it.scope}.${it.name}")]?.let { innerClass ->
|
if (lookupSymbolClassId != null) {
|
||||||
addChangedClass(innerClass)
|
addChangedClass(lookupSymbolClassId)
|
||||||
} ?: addChangedClassMember(classIdOfScope, it.name)
|
} else {
|
||||||
return@forEach
|
// When lookupSymbolClassId == null, it means that either (1) the LookupSymbol does not refer to a class (it refers to
|
||||||
|
// a class member or a package-level member), or (2) it refers to a class outside allClassIds. In the following, we
|
||||||
|
// assume that (1) is the case.
|
||||||
|
// (2) should typically never happen. In the case that it happens, we will collect incorrect changes, but it's
|
||||||
|
// acceptable to collect more changes than necessary; also, we do not need to collect changes outside allClassIds, so
|
||||||
|
// we're not collecting fewer changes than required.
|
||||||
|
val scopeClassId: ClassId? = fqNameToClassId[FqName(it.scope)]
|
||||||
|
if (scopeClassId != null) {
|
||||||
|
addChangedClassMember(scopeClassId, it.name)
|
||||||
|
} else {
|
||||||
|
// Similarly, when scopeClassId == null, we assume that LookupSymbol.scope does not refer to a class outside
|
||||||
|
// allClassIds. It means that the LookupSymbol does not refer to a class member. Therefore, it must refer to a
|
||||||
|
// package-level member.
|
||||||
|
addChangedTopLevelMember(FqName(it.scope), it.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// scope is a package, so changed symbol is a top-level member and maybe a class
|
|
||||||
val potentialClassFqName = if (it.scope.isEmpty()) FqName(it.name) else FqName("${it.scope}.${it.name}")
|
|
||||||
fqNameToClassId[potentialClassFqName]?.let { classId ->
|
|
||||||
// Lookup symbol is a class
|
|
||||||
addChangedClass(classId)
|
|
||||||
} ?: addChangedTopLevelMember(FqName(it.scope), it.name)
|
|
||||||
}
|
}
|
||||||
val changes = getChanges()
|
getChanges()
|
||||||
|
|
||||||
// dirtyClassesFqNames should be derived from dirtyLookupSymbols. Double-check that this is the case.
|
|
||||||
val changedFqNames: Set<FqName> =
|
|
||||||
changes.changedClasses.map { it.asSingleFqName() }.toSet() +
|
|
||||||
changes.changedClassMembers.keys.map { it.asSingleFqName() } +
|
|
||||||
changes.changedTopLevelMembers.keys
|
|
||||||
check(dirtyClassesFqNames.toSet() == changedFqNames) {
|
|
||||||
"Two sets differ:\n" +
|
|
||||||
"dirtyClassesFqNames: $dirtyClassesFqNames\n" +
|
|
||||||
"changedFqNames: $changedFqNames"
|
|
||||||
}
|
|
||||||
changes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DirtyData contains:
|
||||||
|
// 1. dirtyLookupSymbols => This contains all info we need (extracted above).
|
||||||
|
// 2. dirtyClassesFqNames => This should be derived from dirtyLookupSymbols.
|
||||||
|
// 3. dirtyClassesFqNamesForceRecompile => Should be irrelevant.
|
||||||
|
// Double-check that the assumption at bullet 2 above is correct.
|
||||||
|
val changedFqNames: Set<FqName> =
|
||||||
|
changes.changedClasses.map { it.asSingleFqName() }.toSet() +
|
||||||
|
changes.changedClassMembers.keys.map { it.asSingleFqName() } +
|
||||||
|
changes.changedTopLevelMembers.keys
|
||||||
|
check(dirtyClassesFqNames.toSet() == changedFqNames) {
|
||||||
|
"Two sets differ:\n" +
|
||||||
|
"dirtyClassesFqNames: $dirtyClassesFqNames\n" +
|
||||||
|
"changedFqNames: $changedFqNames"
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ClassSnapshot.getClassId(): ClassId {
|
internal object ImpactAnalysis {
|
||||||
return when (this) {
|
|
||||||
is KotlinClassSnapshot -> classInfo.classId
|
|
||||||
is RegularJavaClassSnapshot -> classId
|
|
||||||
is ProtoBasedJavaClassSnapshot -> serializedJavaClass.classId
|
|
||||||
is EmptyJavaClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
|
||||||
error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private object ImpactAnalysis {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the set of classes/class members that are impacted by the given changes.
|
* Computes the set of classes, class members, and top-level members that are impacted by the given changes.
|
||||||
*
|
*
|
||||||
* For example, if a superclass has changed, any of its subclasses will be impacted even if it has not changed, and unchanged source
|
* For example, if a superclass has changed, any of its subclasses will be impacted even if it has not changed because unchanged source
|
||||||
* files in the previous compilation that depended on the subclasses will need to be recompiled.
|
* files in the previous compilation that depended on the subclasses will need to be recompiled.
|
||||||
*
|
*
|
||||||
* The returned set is also a [ChangeSet], which includes the given changes plus the impacted ones.
|
* The returned set is also a [ChangeSet], which includes the given changes plus the impacted ones.
|
||||||
*/
|
*/
|
||||||
fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
||||||
val classIdToSubclasses = getClassIdToSubclassesMap(previousClassSnapshots)
|
val classIdToSubclasses = getClassIdToSubclassesMap(previousClassSnapshots)
|
||||||
|
val impactedClassesResolver = { classId: ClassId -> classIdToSubclasses[classId] ?: emptySet() }
|
||||||
|
|
||||||
return ChangeSet.Collector().run {
|
return ChangeSet.Collector().run {
|
||||||
addChangedClasses(findSubclassesInclusive(changes.changedClasses, classIdToSubclasses))
|
addChangedClasses(findImpactedClassesInclusive(changes.changedClasses, impactedClassesResolver))
|
||||||
for ((changedClass, changedClassMembers) in changes.changedClassMembers) {
|
for ((changedClass, changedClassMembers) in changes.changedClassMembers) {
|
||||||
findSubclassesInclusive(setOf(changedClass), classIdToSubclasses).forEach {
|
findImpactedClassesInclusive(setOf(changedClass), impactedClassesResolver).forEach {
|
||||||
addChangedClassMembers(it, changedClassMembers)
|
addChangedClassMembers(it, changedClassMembers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,7 +289,7 @@ private object ImpactAnalysis {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getClassIdToSubclassesMap(classSnapshots: List<ClassSnapshot>): Map<ClassId, Set<ClassId>> {
|
private fun getClassIdToSubclassesMap(classSnapshots: List<ClassSnapshot>): Map<ClassId, Set<ClassId>> {
|
||||||
val classIds = classSnapshots.map { it.getClassId() }
|
val classIds: Set<ClassId> = classSnapshots.map { it.getClassId() }.toSet() // Use Set for presence check
|
||||||
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
||||||
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
||||||
|
|
||||||
@@ -345,7 +297,7 @@ private object ImpactAnalysis {
|
|||||||
classSnapshots.forEach { classSnapshot ->
|
classSnapshots.forEach { classSnapshot ->
|
||||||
val classId = classSnapshot.getClassId()
|
val classId = classSnapshot.getClassId()
|
||||||
classSnapshot.getSupertypes(classNameToClassIdResolver).forEach { supertype ->
|
classSnapshot.getSupertypes(classNameToClassIdResolver).forEach { supertype ->
|
||||||
// No need to collect supertypes outside the considered class snapshots (e.g., "java/lang/Object")
|
// No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object")
|
||||||
if (supertype in classIds) {
|
if (supertype in classIds) {
|
||||||
classIdToSubclasses.computeIfAbsent(supertype) { mutableSetOf() }.add(classId)
|
classIdToSubclasses.computeIfAbsent(supertype) { mutableSetOf() }.add(classId)
|
||||||
}
|
}
|
||||||
@@ -354,38 +306,17 @@ private object ImpactAnalysis {
|
|||||||
return classIdToSubclasses
|
return classIdToSubclasses
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List<ClassId> {
|
|
||||||
return when (this) {
|
|
||||||
is RegularJavaClassSnapshot -> supertypes.mapNotNull {
|
|
||||||
// The following call returns null if supertype is outside the considered class snapshots (e.g., "java/lang/Object").
|
|
||||||
// Use `mapNotNull` as we don't need to collect those supertypes (see getClassIdToSubclassesMap).
|
|
||||||
classIdResolver.invoke(it)
|
|
||||||
}
|
|
||||||
is KotlinClassSnapshot -> supertypes.mapNotNull {
|
|
||||||
// Same as above
|
|
||||||
classIdResolver.invoke(it)
|
|
||||||
}
|
|
||||||
is ProtoBasedJavaClassSnapshot -> {
|
|
||||||
val (proto, nameResolver) = serializedJavaClass.toProtoData()
|
|
||||||
proto.supertypes(TypeTable(proto.typeTable)).map { nameResolver.getClassId(it.className) }
|
|
||||||
}
|
|
||||||
is EmptyJavaClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
|
||||||
error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds direct and indirect subclasses of the given classes. The return set includes both the given classes and their direct and
|
* Finds directly and transitively impacted classes of the given classes. The return set includes both the given classes and the
|
||||||
* indirect subclasses.
|
* impacted classes.
|
||||||
*/
|
*/
|
||||||
private fun findSubclassesInclusive(classIds: Set<ClassId>, classIdsToSubclasses: Map<ClassId, Set<ClassId>>): Set<ClassId> {
|
fun findImpactedClassesInclusive(classIds: Set<ClassId>, impactedClassesResolver: (ClassId) -> Set<ClassId>): Set<ClassId> {
|
||||||
val visitedClasses = mutableSetOf<ClassId>()
|
val visitedClasses = mutableSetOf<ClassId>()
|
||||||
val toVisitClasses = classIds.toMutableSet()
|
val toVisitClasses = classIds.toMutableSet()
|
||||||
while (toVisitClasses.isNotEmpty()) {
|
while (toVisitClasses.isNotEmpty()) {
|
||||||
val nextToVisit = mutableSetOf<ClassId>()
|
val nextToVisit = mutableSetOf<ClassId>()
|
||||||
toVisitClasses.forEach {
|
toVisitClasses.forEach {
|
||||||
nextToVisit.addAll(classIdsToSubclasses[it] ?: emptyList())
|
nextToVisit.addAll(impactedClassesResolver.invoke(it))
|
||||||
}
|
}
|
||||||
visitedClasses.addAll(toVisitClasses)
|
visitedClasses.addAll(toVisitClasses)
|
||||||
toVisitClasses.clear()
|
toVisitClasses.clear()
|
||||||
@@ -394,3 +325,34 @@ private object ImpactAnalysis {
|
|||||||
return visitedClasses
|
return visitedClasses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ClassSnapshot.getClassId(): ClassId {
|
||||||
|
return when (this) {
|
||||||
|
is KotlinClassSnapshot -> classInfo.classId
|
||||||
|
is RegularJavaClassSnapshot -> classId
|
||||||
|
is ProtoBasedJavaClassSnapshot -> serializedJavaClass.classId
|
||||||
|
is EmptyJavaClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
||||||
|
error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the [ClassId]s of the supertypes of this class.
|
||||||
|
*
|
||||||
|
* @param classIdResolver Resolves the [ClassId] from the [JvmClassName] of a supertype. It may return null if the supertype is outside the
|
||||||
|
* considered set of classes (e.g., "java/lang/Object"). Those supertypes do not need to be included in the returned result.
|
||||||
|
*/
|
||||||
|
internal fun ClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List<ClassId> {
|
||||||
|
return when (this) {
|
||||||
|
is KotlinClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
||||||
|
is RegularJavaClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
||||||
|
is ProtoBasedJavaClassSnapshot -> {
|
||||||
|
val (proto, nameResolver) = serializedJavaClass.toProtoData()
|
||||||
|
proto.supertypes(TypeTable(proto.typeTable)).map { nameResolver.getClassId(it.className) }
|
||||||
|
}
|
||||||
|
is EmptyJavaClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
||||||
|
error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+11
-2
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.incremental.classpathDiff
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||||
import org.jetbrains.kotlin.incremental.SerializedJavaClass
|
import org.jetbrains.kotlin.incremental.SerializedJavaClass
|
||||||
|
import org.jetbrains.kotlin.incremental.md5
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ class ClasspathEntrySnapshot(
|
|||||||
* Maps (Unix-style) relative paths of classes to their snapshots. The paths are relative to the containing classpath entry (directory
|
* Maps (Unix-style) relative paths of classes to their snapshots. The paths are relative to the containing classpath entry (directory
|
||||||
* or jar).
|
* or jar).
|
||||||
*/
|
*/
|
||||||
val classSnapshots: LinkedHashMap<String, ClassSnapshot>
|
val classSnapshots: LinkedHashMap<String, ClassSnapshotWithHash>
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,7 +38,14 @@ class ClasspathEntrySnapshot(
|
|||||||
* `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures,
|
* `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures,
|
||||||
* and should not contain private method signatures, or method implementations.
|
* and should not contain private method signatures, or method implementations.
|
||||||
*/
|
*/
|
||||||
sealed class ClassSnapshot
|
sealed class ClassSnapshot {
|
||||||
|
|
||||||
|
/** Computes the hash of this [ClassSnapshot] and returns a [ClassSnapshotWithHash]. */
|
||||||
|
fun addHash() = ClassSnapshotWithHash(this, ClassSnapshotExternalizer.toByteArray(this).md5())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Contains a [ClassSnapshot] and its hash. */
|
||||||
|
class ClassSnapshotWithHash(val classSnapshot: ClassSnapshot, val hash: Long)
|
||||||
|
|
||||||
/** [ClassSnapshot] of a Kotlin class. */
|
/** [ClassSnapshot] of a Kotlin class. */
|
||||||
class KotlinClassSnapshot(
|
class KotlinClassSnapshot(
|
||||||
|
|||||||
+51
-38
@@ -10,32 +10,59 @@ import org.jetbrains.kotlin.incremental.JavaClassProtoMapValueExternalizer
|
|||||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||||
import org.jetbrains.kotlin.incremental.storage.*
|
import org.jetbrains.kotlin.incremental.storage.*
|
||||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||||
import java.io.*
|
import java.io.DataInput
|
||||||
|
import java.io.DataOutput
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
/** Utility to serialize a [ClasspathSnapshot]. */
|
/** Utility to serialize a [ClasspathSnapshot]. */
|
||||||
object ClasspathSnapshotSerializer {
|
object CachedClasspathSnapshotSerializer {
|
||||||
|
private val cache = ConcurrentHashMap<File, ClasspathEntrySnapshot>()
|
||||||
|
private const val RECOMMENDED_MAX_CACHE_SIZE = 100
|
||||||
|
|
||||||
fun load(classpathEntrySnapshotFiles: List<File>): ClasspathSnapshot {
|
fun load(classpathEntrySnapshotFiles: List<File>): ClasspathSnapshot {
|
||||||
return ClasspathSnapshot(classpathEntrySnapshotFiles.map {
|
return ClasspathSnapshot(classpathEntrySnapshotFiles.map { snapshotFile ->
|
||||||
ClasspathEntrySnapshotSerializer.load(it)
|
cache.computeIfAbsent(snapshotFile) {
|
||||||
})
|
ClasspathEntrySnapshotExternalizer.loadFromFile(it)
|
||||||
|
}
|
||||||
|
}).also {
|
||||||
|
handleCacheEviction(recentlyReferencedKeys = classpathEntrySnapshotFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCacheEviction(recentlyReferencedKeys: List<File>) {
|
||||||
|
if (cache.size > RECOMMENDED_MAX_CACHE_SIZE) {
|
||||||
|
// Remove old entries.
|
||||||
|
// Note:
|
||||||
|
// - The cache entries after eviction = recently-referenced entries + some other entries (so that
|
||||||
|
// size = RECOMMENDED_MAX_CACHE_SIZE)
|
||||||
|
// + Removed entries don't have to be the oldest (for simplicity).
|
||||||
|
// + If recentlyReferencedKeys.size > RECOMMENDED_MAX_CACHE_SIZE, all of them will be kept. The reason is that
|
||||||
|
// recently-referenced entries will likely be used again, so we keep them even if the cache is larger than recommended.
|
||||||
|
// - It's okay to have race condition in this method.
|
||||||
|
val oldKeys = cache.keys - recentlyReferencedKeys.toSet()
|
||||||
|
for (oldKey in oldKeys) {
|
||||||
|
cache.remove(oldKey)
|
||||||
|
if (cache.size <= RECOMMENDED_MAX_CACHE_SIZE) break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ClasspathEntrySnapshotSerializer : DataSerializer<ClasspathEntrySnapshot> {
|
object ClasspathEntrySnapshotExternalizer : DataExternalizer<ClasspathEntrySnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) {
|
override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) {
|
||||||
LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotDataSerializer).save(output, snapshot.classSnapshots)
|
LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotWithHashExternalizer).save(output, snapshot.classSnapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: DataInput): ClasspathEntrySnapshot {
|
override fun read(input: DataInput): ClasspathEntrySnapshot {
|
||||||
return ClasspathEntrySnapshot(
|
return ClasspathEntrySnapshot(
|
||||||
classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotDataSerializer).read(input)
|
classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotWithHashExternalizer).read(input)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ClassSnapshotDataSerializer : DataSerializer<ClassSnapshot> {
|
object ClassSnapshotExternalizer : DataExternalizer<ClassSnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: ClassSnapshot) {
|
override fun save(output: DataOutput, snapshot: ClassSnapshot) {
|
||||||
output.writeBoolean(snapshot is KotlinClassSnapshot)
|
output.writeBoolean(snapshot is KotlinClassSnapshot)
|
||||||
@@ -55,6 +82,21 @@ object ClassSnapshotDataSerializer : DataSerializer<ClassSnapshot> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ClassSnapshotWithHashExternalizer : DataExternalizer<ClassSnapshotWithHash> {
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, snapshot: ClassSnapshotWithHash) {
|
||||||
|
ClassSnapshotExternalizer.save(output, snapshot.classSnapshot)
|
||||||
|
LongExternalizer.save(output, snapshot.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(input: DataInput): ClassSnapshotWithHash {
|
||||||
|
return ClassSnapshotWithHash(
|
||||||
|
classSnapshot = ClassSnapshotExternalizer.read(input),
|
||||||
|
hash = LongExternalizer.read(input)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object KotlinClassSnapshotExternalizer : DataExternalizer<KotlinClassSnapshot> {
|
object KotlinClassSnapshotExternalizer : DataExternalizer<KotlinClassSnapshot> {
|
||||||
|
|
||||||
override fun save(output: DataOutput, snapshot: KotlinClassSnapshot) {
|
override fun save(output: DataOutput, snapshot: KotlinClassSnapshot) {
|
||||||
@@ -183,32 +225,3 @@ object ContentHashJavaClassSnapshotExternalizer : DataExternalizer<ContentHashJa
|
|||||||
return ContentHashJavaClassSnapshot(contentHash = LongExternalizer.read(input))
|
return ContentHashJavaClassSnapshot(contentHash = LongExternalizer.read(input))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DataSerializer<T> : DataExternalizer<T> {
|
|
||||||
|
|
||||||
fun save(file: File, value: T) {
|
|
||||||
return DataOutputStream(FileOutputStream(file).buffered()).use {
|
|
||||||
save(it, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun load(file: File): T {
|
|
||||||
return DataInputStream(FileInputStream(file).buffered()).use {
|
|
||||||
read(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toByteArray(value: T): ByteArray {
|
|
||||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
|
||||||
DataOutputStream(byteArrayOutputStream.buffered()).use {
|
|
||||||
save(it, value)
|
|
||||||
}
|
|
||||||
return byteArrayOutputStream.toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromByteArray(byteArray: ByteArray): T {
|
|
||||||
return DataInputStream(ByteArrayInputStream(byteArray).buffered()).use {
|
|
||||||
read(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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.incremental.classpathDiff
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.measure
|
||||||
|
import org.jetbrains.kotlin.incremental.LookupStorage
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.LookupSymbolKey
|
||||||
|
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
|
|
||||||
|
object ClasspathSnapshotShrinker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrinks the given [ClasspathSnapshot] by retaining only classes that are referenced. Referencing info is stored in [LookupStorage].
|
||||||
|
*
|
||||||
|
* This method also removes duplicate classes and [EmptyJavaClassSnapshot]s first.
|
||||||
|
*/
|
||||||
|
fun shrink(
|
||||||
|
classpathSnapshot: ClasspathSnapshot,
|
||||||
|
lookupStorage: LookupStorage,
|
||||||
|
metrics: BuildMetricsReporter
|
||||||
|
): List<ClassSnapshotWithHash> {
|
||||||
|
val allClasses = metrics.measure(BuildTime.GET_NON_DUPLICATE_CLASSES) {
|
||||||
|
// It's important to remove duplicate classes first before removing `EmptyJavaClassSnapshot`s.
|
||||||
|
// For example, if jar1!/com/example/A.class is empty and jar2!/com/example/A.class is non-empty, incorrect order of the actions
|
||||||
|
// will lead to incorrect results.
|
||||||
|
classpathSnapshot.getNonDuplicateClassSnapshots().filter { it.classSnapshot !is EmptyJavaClassSnapshot }
|
||||||
|
}
|
||||||
|
val lookupSymbols = metrics.measure(BuildTime.GET_LOOKUP_SYMBOLS) {
|
||||||
|
lookupStorage.lookupMap.keys
|
||||||
|
}
|
||||||
|
val referencedClasses = metrics.measure(BuildTime.FIND_REFERENCED_CLASSES) {
|
||||||
|
findReferencedClasses(allClasses, lookupSymbols)
|
||||||
|
}
|
||||||
|
return metrics.measure(BuildTime.FIND_TRANSITIVELY_REFERENCED_CLASSES) {
|
||||||
|
findTransitivelyReferencedClasses(allClasses, referencedClasses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds classes that are referenced. Referencing info is stored in [LookupStorage].
|
||||||
|
*
|
||||||
|
* Note: It's okay to over-approximate referenced classes.
|
||||||
|
*/
|
||||||
|
private fun findReferencedClasses(
|
||||||
|
allClasses: List<ClassSnapshotWithHash>,
|
||||||
|
lookupSymbols: Collection<LookupSymbolKey>
|
||||||
|
): List<ClassSnapshotWithHash> {
|
||||||
|
val potentialClassNamesOfReferencedClasses =
|
||||||
|
lookupSymbols.flatMap {
|
||||||
|
val lookupSymbolFqName = if (it.scope.isEmpty()) FqName(it.name) else FqName("${it.scope}.${it.name}")
|
||||||
|
listOf(
|
||||||
|
lookupSymbolFqName, // If LookupSymbol refers to a class, the class's FqName will be captured here.
|
||||||
|
FqName(it.scope) // If LookupSymbol refers to a class member, the class's FqName will be captured here.
|
||||||
|
)
|
||||||
|
}.toSet() // Use Set for presence check
|
||||||
|
val potentialPackageNamesOfReferencedPackageLevelMembers =
|
||||||
|
lookupSymbols.map {
|
||||||
|
FqName(it.scope) // If LookupSymbol refers to a package-level member, the package's FqName will be captured here.
|
||||||
|
}.toSet() // Use Set for presence check
|
||||||
|
|
||||||
|
return allClasses.filter {
|
||||||
|
val classId = it.classSnapshot.getClassId()
|
||||||
|
|
||||||
|
(classId.asSingleFqName() in potentialClassNamesOfReferencedClasses) ||
|
||||||
|
(it.classSnapshot is KotlinClassSnapshot
|
||||||
|
&& it.classSnapshot.classInfo.classKind != KotlinClassHeader.Kind.CLASS
|
||||||
|
&& classId.packageFqName in potentialPackageNamesOfReferencedPackageLevelMembers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds classes that are transitively referenced. For example, if a subclass is referenced, its supertypes will potentially be
|
||||||
|
* referenced.
|
||||||
|
*
|
||||||
|
* The returned list includes the given referenced classes plus the transitively referenced ones.
|
||||||
|
*/
|
||||||
|
private fun findTransitivelyReferencedClasses(
|
||||||
|
allClasses: List<ClassSnapshotWithHash>,
|
||||||
|
referencedClasses: List<ClassSnapshotWithHash>
|
||||||
|
): List<ClassSnapshotWithHash> {
|
||||||
|
val classIdToClassSnapshot = allClasses.associateBy { it.classSnapshot.getClassId() }
|
||||||
|
val classIds: Set<ClassId> = classIdToClassSnapshot.keys // Use Set for presence check
|
||||||
|
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
||||||
|
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
||||||
|
|
||||||
|
val supertypesResolver = { classId: ClassId ->
|
||||||
|
// No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object")
|
||||||
|
@Suppress("SimpleRedundantLet")
|
||||||
|
classIdToClassSnapshot[classId]?.let {
|
||||||
|
it.classSnapshot.getSupertypes(classNameToClassIdResolver).filter { supertype -> supertype in classIds }.toSet()
|
||||||
|
} ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
val referencedClassIds = referencedClasses.map { it.classSnapshot.getClassId() }.toSet()
|
||||||
|
val transitivelyReferencedClassIds: Set<ClassId> =
|
||||||
|
ImpactAnalysis.findImpactedClassesInclusive(referencedClassIds, supertypesResolver) // Use Set for presence check
|
||||||
|
|
||||||
|
return allClasses.filter { it.classSnapshot.getClassId() in transitivelyReferencedClassIds }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all [ClassSnapshot]s in this [ClasspathSnapshot].
|
||||||
|
*
|
||||||
|
* If there are duplicate classes on the classpath, retain only the first one to match the compiler's behavior.
|
||||||
|
*/
|
||||||
|
internal fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshotWithHash> {
|
||||||
|
val classSnapshots = LinkedHashMap<String, ClassSnapshotWithHash>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
||||||
|
for (classpathEntrySnapshot in classpathEntrySnapshots) {
|
||||||
|
for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) {
|
||||||
|
classSnapshots.putIfAbsent(unixStyleRelativePath, classSnapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return classSnapshots.values.toList()
|
||||||
|
}
|
||||||
+2
-2
@@ -38,10 +38,10 @@ object ClasspathEntrySnapshotter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val snapshots = try {
|
val snapshots = try {
|
||||||
ClassSnapshotter.snapshot(classes, protoBased)
|
ClassSnapshotter.snapshot(classes, protoBased).map { it.addHash() }
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
if (isKnownProblematicClasspathEntry(classpathEntry)) {
|
if (isKnownProblematicClasspathEntry(classpathEntry)) {
|
||||||
classes.map { ContentHashJavaClassSnapshot(it.contents.md5()) }
|
classes.map { ContentHashJavaClassSnapshot(it.contents.md5()).addHash() }
|
||||||
} else throw e
|
} else throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+36
-16
@@ -5,11 +5,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
||||||
|
import org.jetbrains.kotlin.incremental.ChangesEither
|
||||||
|
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.compileAll
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.compileAll
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshot
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshot
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshotAll
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshotAll
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
|
||||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
|
||||||
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
@@ -46,7 +47,7 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
val sourceFile = SimpleKotlinClass(tmpDir)
|
val sourceFile = SimpleKotlinClass(tmpDir)
|
||||||
val previousSnapshot = sourceFile.compileAndSnapshot()
|
val previousSnapshot = sourceFile.compileAndSnapshot()
|
||||||
val currentSnapshot = sourceFile.changePublicMethodSignature().compileAndSnapshot()
|
val currentSnapshot = sourceFile.changePublicMethodSignature().compileAndSnapshot()
|
||||||
val changes = ClasspathChangesComputer.computeClassChanges(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
val changes = computeClassChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
@@ -62,7 +63,7 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
val sourceFile = SimpleKotlinClass(tmpDir)
|
val sourceFile = SimpleKotlinClass(tmpDir)
|
||||||
val previousSnapshot = sourceFile.compileAndSnapshot()
|
val previousSnapshot = sourceFile.compileAndSnapshot()
|
||||||
val currentSnapshot = sourceFile.changeMethodImplementation().compileAndSnapshot()
|
val currentSnapshot = sourceFile.changeMethodImplementation().compileAndSnapshot()
|
||||||
val changes = ClasspathChangesComputer.computeClassChanges(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
val changes = computeClassChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(emptySet(), emptySet()).assertEquals(changes)
|
Changes(emptySet(), emptySet()).assertEquals(changes)
|
||||||
}
|
}
|
||||||
@@ -72,7 +73,7 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testVariousAbiChanges/src/kotlin").canonicalFile
|
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testVariousAbiChanges/src/kotlin").canonicalFile
|
||||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
||||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
||||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
val changes = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
@@ -121,7 +122,7 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
|||||||
File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinOrJava/src/kotlin").canonicalFile
|
File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinOrJava/src/kotlin").canonicalFile
|
||||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
||||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
||||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
val changes = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
@@ -158,7 +159,7 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
|
|||||||
val sourceFile = SimpleJavaClass(tmpDir)
|
val sourceFile = SimpleJavaClass(tmpDir)
|
||||||
val previousSnapshot = sourceFile.compile().snapshot(protoBased)
|
val previousSnapshot = sourceFile.compile().snapshot(protoBased)
|
||||||
val currentSnapshot = sourceFile.changePublicMethodSignature().compile().snapshot(protoBased)
|
val currentSnapshot = sourceFile.changePublicMethodSignature().compile().snapshot(protoBased)
|
||||||
val changes = ClasspathChangesComputer.computeClassChanges(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
val changes = computeClassChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
@@ -174,7 +175,7 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
|
|||||||
val sourceFile = SimpleJavaClass(tmpDir)
|
val sourceFile = SimpleJavaClass(tmpDir)
|
||||||
val previousSnapshot = sourceFile.compile().snapshot(protoBased)
|
val previousSnapshot = sourceFile.compile().snapshot(protoBased)
|
||||||
val currentSnapshot = sourceFile.changeMethodImplementation().compile().snapshot(protoBased)
|
val currentSnapshot = sourceFile.changeMethodImplementation().compile().snapshot(protoBased)
|
||||||
val changes = ClasspathChangesComputer.computeClassChanges(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
val changes = computeClassChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(emptySet(), emptySet()).assertEquals(changes)
|
Changes(emptySet(), emptySet()).assertEquals(changes)
|
||||||
}
|
}
|
||||||
@@ -184,7 +185,7 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
|
|||||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testVariousAbiChanges/src/java").canonicalFile
|
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testVariousAbiChanges/src/java").canonicalFile
|
||||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
|
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
|
||||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
|
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
|
||||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
val changes = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
@@ -229,7 +230,7 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
|
|||||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinOrJava/src/java").canonicalFile
|
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinOrJava/src/java").canonicalFile
|
||||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
|
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
|
||||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
|
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
|
||||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
val changes = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
@@ -259,7 +260,7 @@ class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon()
|
|||||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinAndJava/src").canonicalFile
|
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinAndJava/src").canonicalFile
|
||||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
||||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
||||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
val changes = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||||
|
|
||||||
Changes(
|
Changes(
|
||||||
lookupSymbols = setOf(
|
lookupSymbols = setOf(
|
||||||
@@ -301,7 +302,7 @@ private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder,
|
|||||||
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
|
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
|
||||||
|
|
||||||
val relativePaths = classFiles.map { it.unixStyleRelativePath }
|
val relativePaths = classFiles.map { it.unixStyleRelativePath }
|
||||||
val classSnapshots = classFiles.snapshotAll(protoBased)
|
val classSnapshots = classFiles.snapshotAll(protoBased).map { it.addHash() }
|
||||||
ClasspathEntrySnapshot(
|
ClasspathEntrySnapshot(
|
||||||
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
||||||
)
|
)
|
||||||
@@ -309,12 +310,31 @@ private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder,
|
|||||||
return ClasspathSnapshot(classpathEntrySnapshots)
|
return ClasspathSnapshot(classpathEntrySnapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adapted version of [ClasspathChanges.Available] for readability in this test. */
|
private fun computeClasspathChanges(
|
||||||
|
currentClasspathSnapshot: ClasspathSnapshot,
|
||||||
|
previousClasspathSnapshot: ClasspathSnapshot
|
||||||
|
): Changes {
|
||||||
|
return ClasspathChangesComputer.computeChangedAndImpactedSet(
|
||||||
|
currentClasspathSnapshot.getNonDuplicateClassSnapshots(),
|
||||||
|
previousClasspathSnapshot.getNonDuplicateClassSnapshots(),
|
||||||
|
DoNothingBuildMetricsReporter
|
||||||
|
).normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeClassChanges(currentClassSnapshot: ClassSnapshot, previousClassSnapshot: ClassSnapshot): Changes {
|
||||||
|
return ClasspathChangesComputer.computeClassChanges(
|
||||||
|
listOf(currentClassSnapshot),
|
||||||
|
listOf(previousClassSnapshot),
|
||||||
|
DoNothingBuildMetricsReporter
|
||||||
|
).normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adapted version of [ChangesEither.Known] for readability in this test. */
|
||||||
private data class Changes(val lookupSymbols: Set<LookupSymbol>, val fqNames: Set<String>)
|
private data class Changes(val lookupSymbols: Set<LookupSymbol>, val fqNames: Set<String>)
|
||||||
|
|
||||||
private fun ClasspathChanges.normalize(): Changes {
|
private fun ChangeSet.normalize(): Changes {
|
||||||
this as ClasspathChanges.Available
|
val changes: ChangesEither.Known = getChanges()
|
||||||
return Changes(lookupSymbols, fqNames.map { it.asString() }.toSet())
|
return Changes(changes.lookupSymbols.toSet(), changes.fqNames.map { it.asString() }.toSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Changes.assertEquals(actual: Changes) {
|
private fun Changes.assertEquals(actual: Changes) {
|
||||||
|
|||||||
+6
-4
@@ -6,6 +6,8 @@
|
|||||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||||
|
|
||||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.readBytes
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.readBytes
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.fromByteArray
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -16,8 +18,8 @@ abstract class ClasspathSnapshotSerializerTest : ClasspathSnapshotTestCommon() {
|
|||||||
@Test
|
@Test
|
||||||
open fun `test ClassSnapshotDataSerializer`() {
|
open fun `test ClassSnapshotDataSerializer`() {
|
||||||
val originalSnapshot = testSourceFile.compileAndSnapshot()
|
val originalSnapshot = testSourceFile.compileAndSnapshot()
|
||||||
val serializedSnapshot = ClassSnapshotDataSerializer.toByteArray(originalSnapshot)
|
val serializedSnapshot = ClassSnapshotExternalizer.toByteArray(originalSnapshot)
|
||||||
val deserializedSnapshot = ClassSnapshotDataSerializer.fromByteArray(serializedSnapshot)
|
val deserializedSnapshot = ClassSnapshotExternalizer.fromByteArray(serializedSnapshot)
|
||||||
|
|
||||||
assertEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
|
assertEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
|
||||||
}
|
}
|
||||||
@@ -36,8 +38,8 @@ class JavaClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTe
|
|||||||
val originalSnapshot = testSourceFile.compile().let {
|
val originalSnapshot = testSourceFile.compile().let {
|
||||||
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInSnapshot = false)
|
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInSnapshot = false)
|
||||||
}.single()
|
}.single()
|
||||||
val serializedSnapshot = ClassSnapshotDataSerializer.toByteArray(originalSnapshot)
|
val serializedSnapshot = ClassSnapshotExternalizer.toByteArray(originalSnapshot)
|
||||||
val deserializedSnapshot = ClassSnapshotDataSerializer.fromByteArray(serializedSnapshot)
|
val deserializedSnapshot = ClassSnapshotExternalizer.fromByteArray(serializedSnapshot)
|
||||||
|
|
||||||
assertEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
|
assertEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* 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.incremental.classpathDiff
|
||||||
|
|
||||||
|
// TODO: Write this test
|
||||||
|
abstract class ClasspathSnapshotShrinkerTest
|
||||||
+1
-1
@@ -21,7 +21,7 @@ abstract class ClasspathSnapshotTestCommon {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val testDataDir =
|
val testDataDir =
|
||||||
File("libraries/tools/kotlin-gradle-plugin/src/testData/org/jetbrains/kotlin/gradle/incremental/ClasspathSnapshotTestCommon")
|
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotTestCommon")
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
|
|||||||
+4
-3
@@ -9,8 +9,9 @@ import org.gradle.api.artifacts.transform.*
|
|||||||
import org.gradle.api.file.FileSystemLocation
|
import org.gradle.api.file.FileSystemLocation
|
||||||
import org.gradle.api.provider.Provider
|
import org.gradle.api.provider.Provider
|
||||||
import org.gradle.api.tasks.Classpath
|
import org.gradle.api.tasks.Classpath
|
||||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathEntrySnapshotSerializer
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathEntrySnapshotExternalizer
|
||||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathEntrySnapshotter
|
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathEntrySnapshotter
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.saveToFile
|
||||||
|
|
||||||
/** Transform to create a snapshot of a classpath entry (directory or jar). */
|
/** Transform to create a snapshot of a classpath entry (directory or jar). */
|
||||||
@CacheableTransform
|
@CacheableTransform
|
||||||
@@ -25,6 +26,6 @@ abstract class ClasspathEntrySnapshotTransform : TransformAction<TransformParame
|
|||||||
val snapshotFile = outputs.file(classpathEntry.name.replace('.', '_') + "-snapshot.bin")
|
val snapshotFile = outputs.file(classpathEntry.name.replace('.', '_') + "-snapshot.bin")
|
||||||
|
|
||||||
val snapshot = ClasspathEntrySnapshotter.snapshot(classpathEntry)
|
val snapshot = ClasspathEntrySnapshotter.snapshot(classpathEntry)
|
||||||
ClasspathEntrySnapshotSerializer.save(snapshotFile, snapshot)
|
ClasspathEntrySnapshotExternalizer.saveToFile(snapshotFile, snapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-77
@@ -55,6 +55,9 @@ import org.jetbrains.kotlin.gradle.targets.js.ir.isProduceUnzippedKlib
|
|||||||
import org.jetbrains.kotlin.gradle.utils.*
|
import org.jetbrains.kotlin.gradle.utils.*
|
||||||
import org.jetbrains.kotlin.incremental.ChangedFiles
|
import org.jetbrains.kotlin.incremental.ChangedFiles
|
||||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
||||||
|
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotDisabled
|
||||||
|
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.*
|
||||||
|
import org.jetbrains.kotlin.incremental.ClasspathSnapshotFiles
|
||||||
import org.jetbrains.kotlin.incremental.IncrementalCompilerRunner
|
import org.jetbrains.kotlin.incremental.IncrementalCompilerRunner
|
||||||
import org.jetbrains.kotlin.library.impl.isKotlinLibrary
|
import org.jetbrains.kotlin.library.impl.isKotlinLibrary
|
||||||
import org.jetbrains.kotlin.statistics.BuildSessionLogger
|
import org.jetbrains.kotlin.statistics.BuildSessionLogger
|
||||||
@@ -584,7 +587,6 @@ abstract class KotlinCompile @Inject constructor(
|
|||||||
)
|
)
|
||||||
val classpathSnapshotDir = getClasspathSnapshotDir(task)
|
val classpathSnapshotDir = getClasspathSnapshotDir(task)
|
||||||
task.classpathSnapshotProperties.classpathSnapshotDir.value(classpathSnapshotDir).disallowChanges()
|
task.classpathSnapshotProperties.classpathSnapshotDir.value(classpathSnapshotDir).disallowChanges()
|
||||||
task.classpathSnapshotProperties.classpathSnapshotDirFileCollection.from(classpathSnapshotDir)
|
|
||||||
} else {
|
} else {
|
||||||
task.classpathSnapshotProperties.classpath.from(task.project.provider { task.classpath })
|
task.classpathSnapshotProperties.classpath.from(task.project.provider { task.classpath })
|
||||||
}
|
}
|
||||||
@@ -644,14 +646,6 @@ abstract class KotlinCompile @Inject constructor(
|
|||||||
@get:OutputDirectory
|
@get:OutputDirectory
|
||||||
@get:Optional // Set if useClasspathSnapshot == true
|
@get:Optional // Set if useClasspathSnapshot == true
|
||||||
abstract val classpathSnapshotDir: DirectoryProperty
|
abstract val classpathSnapshotDir: DirectoryProperty
|
||||||
|
|
||||||
/**
|
|
||||||
* [FileCollection] containing a single file which is [classpathSnapshotDir], used when a [FileCollection] is required instead of a
|
|
||||||
* [DirectoryProperty].
|
|
||||||
*/
|
|
||||||
// Set if useClasspathSnapshot == true
|
|
||||||
@get:Internal
|
|
||||||
abstract val classpathSnapshotDirFileCollection: ConfigurableFileCollection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Internal
|
@get:Internal
|
||||||
@@ -740,15 +734,10 @@ abstract class KotlinCompile @Inject constructor(
|
|||||||
val compilerRunner = compilerRunner.get()
|
val compilerRunner = compilerRunner.get()
|
||||||
|
|
||||||
val icEnv = if (isIncrementalCompilationEnabled()) {
|
val icEnv = if (isIncrementalCompilationEnabled()) {
|
||||||
val classpathChanges = when {
|
|
||||||
!classpathSnapshotProperties.useClasspathSnapshot.get() -> ClasspathChanges.NotAvailable.ClasspathSnapshotIsDisabled
|
|
||||||
inputChanges.isIncremental -> getClasspathChanges(inputChanges)
|
|
||||||
else -> ClasspathChanges.NotAvailable.ForNonIncrementalRun
|
|
||||||
}
|
|
||||||
logger.info(USING_JVM_INCREMENTAL_COMPILATION_MESSAGE)
|
logger.info(USING_JVM_INCREMENTAL_COMPILATION_MESSAGE)
|
||||||
IncrementalCompilationEnvironment(
|
IncrementalCompilationEnvironment(
|
||||||
changedFiles = getChangedFiles(inputChanges, incrementalProps),
|
changedFiles = getChangedFiles(inputChanges, incrementalProps),
|
||||||
classpathChanges = classpathChanges,
|
classpathChanges = getClasspathChanges(inputChanges),
|
||||||
workingDir = taskBuildDirectory.get().asFile,
|
workingDir = taskBuildDirectory.get().asFile,
|
||||||
usePreciseJavaTracking = usePreciseJavaTracking,
|
usePreciseJavaTracking = usePreciseJavaTracking,
|
||||||
disableMultiModuleIC = disableMultiModuleIC,
|
disableMultiModuleIC = disableMultiModuleIC,
|
||||||
@@ -756,16 +745,9 @@ abstract class KotlinCompile @Inject constructor(
|
|||||||
)
|
)
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
with(classpathSnapshotProperties) {
|
|
||||||
if (isIncrementalCompilationEnabled() && useClasspathSnapshot.get()) {
|
|
||||||
copyCurrentClasspathEntrySnapshotFiles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val environment = GradleCompilerEnvironment(
|
val environment = GradleCompilerEnvironment(
|
||||||
defaultCompilerClasspath, messageCollector, outputItemCollector,
|
defaultCompilerClasspath, messageCollector, outputItemCollector,
|
||||||
// The compiler runner should not manage (read, modify, or delete) classpathSnapshotDir
|
outputFiles = allOutputFiles(),
|
||||||
outputFiles = allOutputFiles().minus(classpathSnapshotProperties.classpathSnapshotDirFileCollection),
|
|
||||||
reportingSettings = reportingSettings,
|
reportingSettings = reportingSettings,
|
||||||
incrementalCompilationEnvironment = icEnv,
|
incrementalCompilationEnvironment = icEnv,
|
||||||
kotlinScriptExtensions = sourceFilesExtensions.get().toTypedArray()
|
kotlinScriptExtensions = sourceFilesExtensions.get().toTypedArray()
|
||||||
@@ -848,62 +830,29 @@ abstract class KotlinCompile @Inject constructor(
|
|||||||
return super.source(*sources)
|
return super.source(*sources)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getClasspathChanges(inputChanges: InputChanges): ClasspathChanges {
|
private fun getClasspathChanges(inputChanges: InputChanges): ClasspathChanges = when {
|
||||||
val fileChanges = inputChanges.getFileChanges(classpathSnapshotProperties.classpathSnapshot).toList()
|
!classpathSnapshotProperties.useClasspathSnapshot.get() -> ClasspathSnapshotDisabled
|
||||||
return if (fileChanges.isEmpty()) {
|
else -> {
|
||||||
ClasspathChanges.Available(LinkedHashSet(), LinkedHashSet())
|
val classpathSnapshotFiles = ClasspathSnapshotFiles(
|
||||||
} else {
|
classpathSnapshotProperties.classpathSnapshot.files.toList(),
|
||||||
val previousClasspathEntrySnapshotFiles = getPreviousClasspathEntrySnapshotFiles()
|
classpathSnapshotProperties.classpathSnapshotDir.get().asFile
|
||||||
if (previousClasspathEntrySnapshotFiles.isEmpty()) {
|
)
|
||||||
// When this happens, it means that either the previous classpath was empty or there were no source files to compile in the
|
when {
|
||||||
// previous non-incremental run so the task action was skipped and the classpath snapshot directory was not populated (see
|
!inputChanges.isIncremental -> NotAvailableForNonIncrementalRun(classpathSnapshotFiles)
|
||||||
// AbstractKotlinCompile.executeImpl).
|
inputChanges.getFileChanges(classpathSnapshotProperties.classpathSnapshot).none() -> Empty(classpathSnapshotFiles)
|
||||||
// We could improve this handling, but it's fine to return `UnableToCompute` here as it's likely that there are also no
|
!classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile.exists() -> {
|
||||||
// source files to compile/recompile in this incremental run.
|
// When this happens, it means that the classpath snapshot in the previous run was not saved for some reason. It's
|
||||||
ClasspathChanges.NotAvailable.UnableToCompute
|
// likely that there were no source files to compile, so the task action was skipped (see
|
||||||
} else {
|
// AbstractKotlinCompile.executeImpl), and therefore the classpath snapshot was not saved.
|
||||||
val currentClasspathEntrySnapshotFiles = classpathSnapshotProperties.classpathSnapshot.files.toList()
|
// Missing classpath snapshot will make this run non-incremental, but because there were no source files to compile in
|
||||||
val changedCurrentFiles = fileChanges
|
// the previous run, *all* source files in this run (if there are any) need to be compiled anyway, so being
|
||||||
.filter { it.changeType == ChangeType.ADDED || it.changeType == ChangeType.MODIFIED }
|
// non-incremental is actually okay.
|
||||||
.map { it.file }.toSet()
|
NotAvailableDueToMissingClasspathSnapshot(classpathSnapshotFiles)
|
||||||
ClasspathChangesComputer.compute(
|
}
|
||||||
currentClasspathEntrySnapshotFiles = currentClasspathEntrySnapshotFiles,
|
else -> ToBeComputedByIncrementalCompiler(classpathSnapshotFiles)
|
||||||
previousClasspathEntrySnapshotFiles = previousClasspathEntrySnapshotFiles,
|
|
||||||
unchangedCurrentClasspathEntrySnapshotFiles = currentClasspathEntrySnapshotFiles.filter { it !in changedCurrentFiles }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies classpath entry snapshot files of the current build to `classpathSnapshotDir`. They will be used in the next build (see
|
|
||||||
* [getPreviousClasspathEntrySnapshotFiles]).
|
|
||||||
*
|
|
||||||
* To preserve their order, we put them in subdirectories with names being their indices, as shown below:
|
|
||||||
* classpathSnapshotDir/0/a-snapshot.bin
|
|
||||||
* classpathSnapshotDir/1/b-snapshot.bin
|
|
||||||
* ...
|
|
||||||
* classpathSnapshotDir/N-1/z-snapshot.bin
|
|
||||||
*/
|
|
||||||
private fun copyCurrentClasspathEntrySnapshotFiles() {
|
|
||||||
val snapshotFiles = classpathSnapshotProperties.classpathSnapshot.files.toList()
|
|
||||||
val classpathSnapshotDir = classpathSnapshotProperties.classpathSnapshotDir.get().asFile
|
|
||||||
classpathSnapshotDir.deleteRecursively()
|
|
||||||
classpathSnapshotDir.mkdirs()
|
|
||||||
snapshotFiles.forEachIndexed { index, snapshotFile ->
|
|
||||||
snapshotFile.copyTo(File("$classpathSnapshotDir/$index/${snapshotFile.name}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns classpath entry snapshot files of the previous build, stored in `classpathSnapshotDir` (see
|
|
||||||
* [copyCurrentClasspathEntrySnapshotFiles]).
|
|
||||||
*/
|
|
||||||
private fun getPreviousClasspathEntrySnapshotFiles(): List<File> {
|
|
||||||
val classpathSnapshotDir = classpathSnapshotProperties.classpathSnapshotDir.get().asFile
|
|
||||||
val subDirs = classpathSnapshotDir.listFiles()!!.sortedBy { it.name.toInt() }
|
|
||||||
return subDirs.map { it.listFiles()!!.single() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@CacheableTask
|
@CacheableTask
|
||||||
@@ -1152,7 +1101,7 @@ abstract class Kotlin2JsCompile @Inject constructor(
|
|||||||
logger.info(USING_JS_INCREMENTAL_COMPILATION_MESSAGE)
|
logger.info(USING_JS_INCREMENTAL_COMPILATION_MESSAGE)
|
||||||
IncrementalCompilationEnvironment(
|
IncrementalCompilationEnvironment(
|
||||||
getChangedFiles(inputChanges, incrementalProps),
|
getChangedFiles(inputChanges, incrementalProps),
|
||||||
ClasspathChanges.NotAvailable.ForJSCompiler,
|
ClasspathChanges.NotAvailableForJSCompiler,
|
||||||
taskBuildDirectory.get().asFile,
|
taskBuildDirectory.get().asFile,
|
||||||
multiModuleICSettings = multiModuleICSettings
|
multiModuleICSettings = multiModuleICSettings
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user