Introduce ChangesCollector

This commit is contained in:
Alexey Tsvetkov
2017-08-10 15:59:56 +03:00
parent d4d684a7f0
commit 6fedf07f56
10 changed files with 355 additions and 321 deletions
@@ -0,0 +1,189 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.incremental
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.protobuf.MessageLite
import org.jetbrains.kotlin.serialization.Flags
import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
class ChangesCollector {
private val removedMembers = hashMapOf<FqName, MutableSet<String>>()
private val changedMembers = hashMapOf<FqName, MutableSet<String>>()
private val areSubclassesAffected = hashMapOf<FqName, Boolean>()
fun changes(): List<ChangeInfo> {
val changes = arrayListOf<ChangeInfo>()
for ((fqName, members) in removedMembers) {
if (members.isNotEmpty()) {
changes.add(ChangeInfo.Removed(fqName, members))
}
}
for ((fqName, members) in changedMembers) {
if (members.isNotEmpty()) {
changes.add(ChangeInfo.MembersChanged(fqName, members))
}
}
for ((fqName, areSubclassesAffected) in areSubclassesAffected) {
changes.add(ChangeInfo.SignatureChanged(fqName, areSubclassesAffected))
}
return changes
}
private fun <T, R> MutableMap<T, MutableSet<R>>.getSet(key: T) =
getOrPut(key) { HashSet() }
private fun collectChangedMember(scope: FqName, name: String) {
changedMembers.getSet(scope).add(name)
}
private fun collectRemovedMember(scope: FqName, name: String) {
removedMembers.getSet(scope).add(name)
}
private fun collectChangedMembers(scope: FqName, names: Collection<String>) {
if (names.isNotEmpty()) {
changedMembers.getSet(scope).addAll(names)
}
}
private fun collectRemovedMembers(scope: FqName, names: Collection<String>) {
if (names.isNotEmpty()) {
removedMembers.getSet(scope).addAll(names)
}
}
fun collectProtoChanges(oldData: ProtoData?, newData: ProtoData?) {
if (oldData == null && newData == null) {
throw IllegalStateException("Old and new value are null")
}
if (oldData == null) {
newData!!.collectAll(isRemoved = false)
return
}
if (newData == null) {
oldData.collectAll(isRemoved = true)
return
}
when (oldData) {
is ClassProtoData -> {
when (newData) {
is ClassProtoData -> {
val fqName = oldData.nameResolver.getClassId(oldData.proto.fqName).asSingleFqName()
val diff = DifferenceCalculatorForClass(oldData, newData).difference()
if (diff.isClassAffected) {
collectSignature(oldData, diff.areSubclassesAffected)
}
collectChangedMembers(fqName, diff.changedMembersNames)
}
is PackagePartProtoData -> {
collectSignature(oldData, areSubclassesAffected = true)
}
}
}
is PackagePartProtoData -> {
when (newData) {
is ClassProtoData -> {
collectSignature(newData, areSubclassesAffected = false)
}
is PackagePartProtoData -> {
val diff = DifferenceCalculatorForPackageFacade(oldData, newData).difference()
collectChangedMembers(oldData.packageFqName, diff.changedMembersNames)
}
}
}
}
}
private fun <T> T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List<MessageLite>): Set<String> =
members.flatMap { this.it().filterNot { it.isPrivate }.names(nameResolver) }.toSet()
private fun ProtoData.collectAll(isRemoved: Boolean) =
when (this) {
is PackagePartProtoData -> collectAllFromPackage(isRemoved)
is ClassProtoData -> collectAllFromClass(isRemoved)
}
private fun PackagePartProtoData.collectAllFromPackage(isRemoved: Boolean) {
val memberNames =
proto.getNonPrivateNames(
nameResolver,
ProtoBuf.Package::getFunctionList,
ProtoBuf.Package::getPropertyList
)
if (isRemoved) {
collectRemovedMembers(packageFqName, memberNames)
}
else {
collectChangedMembers(packageFqName, memberNames)
}
}
private fun ClassProtoData.collectAllFromClass(isRemoved: Boolean) {
val classFqName = nameResolver.getClassId(proto.fqName).asSingleFqName()
val kind = Flags.CLASS_KIND.get(proto.flags)
if (kind == ProtoBuf.Class.Kind.COMPANION_OBJECT) {
val memberNames =
proto.getNonPrivateNames(
nameResolver,
ProtoBuf.Class::getConstructorList,
ProtoBuf.Class::getFunctionList,
ProtoBuf.Class::getPropertyList
) + proto.enumEntryList.map { nameResolver.getString(it.name) }
val collectMember = if (isRemoved) this@ChangesCollector::collectRemovedMember else this@ChangesCollector::collectChangedMember
collectMember(classFqName.parent(), classFqName.shortName().asString())
memberNames.forEach { collectMember(classFqName, it) }
}
else {
collectSignature(classFqName, areSubclassesAffected = true)
}
}
fun collectMemberIfValueWasChanged(scope: FqName, name: String, oldValue: Any?, newValue: Any?) {
if (oldValue == null && newValue == null) {
throw IllegalStateException("Old and new value are null for $scope#$name")
}
if (oldValue != null && newValue == null) {
collectRemovedMember(scope, name)
}
else if (oldValue != newValue) {
collectChangedMember(scope, name)
}
}
private fun collectSignature(classData: ClassProtoData, areSubclassesAffected: Boolean) {
val fqName = classData.nameResolver.getClassId(classData.proto.fqName).asSingleFqName()
collectSignature(fqName, areSubclassesAffected)
}
fun collectSignature(fqName: FqName, areSubclassesAffected: Boolean) {
val prevValue = this.areSubclassesAffected[fqName] ?: false
this.areSubclassesAffected[fqName] = prevValue || areSubclassesAffected
}
}
@@ -18,14 +18,11 @@ package org.jetbrains.kotlin.incremental
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
import com.intellij.util.SmartList
import com.intellij.util.io.BooleanDataDescriptor
import com.intellij.util.io.EnumeratorStringDescriptor
import gnu.trove.THashSet
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.build.GeneratedJvmClass
import org.jetbrains.kotlin.incremental.ChangeInfo.MembersChanged
import org.jetbrains.kotlin.incremental.ChangeInfo.Removed
import org.jetbrains.kotlin.incremental.storage.*
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
@@ -33,11 +30,7 @@ import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.protobuf.MessageLite
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.serialization.Flags
import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
import org.jetbrains.org.objectweb.asm.*
@@ -106,15 +99,14 @@ open class IncrementalCacheImpl(
return toSystemIndependentName(File(outputDir, "$internalClassName.class").canonicalPath)
}
fun saveModuleMappingToCache(sourceFiles: Collection<File>, file: File): CompilationResult {
fun saveModuleMappingToCache(sourceFiles: Collection<File>, file: File) {
val jvmClassName = JvmClassName.byInternalName(MODULE_MAPPING_FILE_NAME)
protoMap.storeModuleMapping(jvmClassName, file.readBytes())
dirtyOutputClassesMap.notDirty(MODULE_MAPPING_FILE_NAME)
sourceFiles.forEach { sourceToClassesMap.add(it, jvmClassName) }
return CompilationResult.NO_CHANGES
}
open fun saveFileToCache(generatedClass: GeneratedJvmClass): CompilationResult {
open fun saveFileToCache(generatedClass: GeneratedJvmClass, changesCollector: ChangesCollector) {
val sourceFiles: Collection<File> = generatedClass.sourceFiles
val kotlinClass: LocalFileKotlinClass = generatedClass.outputClass
val className = kotlinClass.className
@@ -126,19 +118,17 @@ open class IncrementalCacheImpl(
internalNameToSource[className.internalName] = sourceFiles
if (kotlinClass.classId.isLocal) {
return CompilationResult.NO_CHANGES
}
if (kotlinClass.classId.isLocal) return
val header = kotlinClass.classHeader
val changesInfo = when (header.kind) {
when (header.kind) {
KotlinClassHeader.Kind.FILE_FACADE -> {
assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className)
protoMap.process(kotlinClass, isPackage = true) +
constantsMap.process(kotlinClass, isPackage = true) +
inlineFunctionsMap.process(kotlinClass, isPackage = true)
protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, changesCollector)
}
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
val partNames = kotlinClass.classHeader.data?.toList()
@@ -148,118 +138,43 @@ open class IncrementalCacheImpl(
// the class' proto wouldn't ever be deleted,
// because we don't write proto for multifile facades.
// As a workaround we can remove proto values for multifile facades.
val additionalChangeInfo = if (className in protoMap) {
val info = ChangeInfo.SignatureChanged(className.fqNameForClassNameWithoutDollars, areSubclassesAffected = true)
CompilationResult(changes = sequenceOf(info))
if (className in protoMap) {
changesCollector.collectSignature(className.fqNameForClassNameWithoutDollars, areSubclassesAffected = true)
}
else CompilationResult.NO_CHANGES
protoMap.remove(className)
protoMap.remove(className, changesCollector)
classFqNameToSourceMap.remove(className.fqNameForClassNameWithoutDollars)
internalNameToSource.remove(className.internalName)
// TODO NO_CHANGES? (delegates only)
constantsMap.process(kotlinClass, isPackage = true) +
inlineFunctionsMap.process(kotlinClass, isPackage = true) +
additionalChangeInfo
constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, changesCollector)
}
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className)
partToMultifileFacade.set(className.internalName, header.multifileClassName!!)
protoMap.process(kotlinClass, isPackage = true) +
constantsMap.process(kotlinClass, isPackage = true) +
inlineFunctionsMap.process(kotlinClass, isPackage = true)
protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, changesCollector)
}
KotlinClassHeader.Kind.CLASS -> {
assert(sourceFiles.size == 1) { "Class is expected to have only one source file: $sourceFiles" }
addToClassStorage(kotlinClass, sourceFiles.first())
protoMap.process(kotlinClass, isPackage = false) +
constantsMap.process(kotlinClass, isPackage = false) +
inlineFunctionsMap.process(kotlinClass, isPackage = false)
}
else -> CompilationResult.NO_CHANGES
}
changesInfo.logIfSomethingChanged(className)
return changesInfo
}
private fun CompilationResult.logIfSomethingChanged(className: JvmClassName) {
if (this == CompilationResult.NO_CHANGES) return
debugLog("$className is changed: $this")
}
private fun computeChanges(className: JvmClassName, createChangeInfo: (FqName, Collection<String>) -> ChangeInfo): List<ChangeInfo> {
if (className.internalName == MODULE_MAPPING_FILE_NAME) return emptyList()
val mapValue = protoMap[className] ?: return emptyList()
return when {
mapValue.isPackageFacade -> {
val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(mapValue.bytes, mapValue.strings)
computePackageChanges(className.packageFqName, packageProto, nameResolver, createChangeInfo)
}
else -> {
val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(mapValue.bytes, mapValue.strings)
computeClassChanges(nameResolver, classProto, createChangeInfo)
protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, changesCollector)
}
}
}
private fun <T> T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List<MessageLite>): Set<String> =
members.flatMap { this.it().filterNot { it.isPrivate }.names(nameResolver) }.toSet()
private fun computePackageChanges(
packageFqName: FqName,
protoData: ProtoBuf.Package,
nameResolver: NameResolver,
createChangeInfo: (FqName, Collection<String>) -> ChangeInfo
): List<ChangeInfo> {
val memberNames =
protoData.getNonPrivateNames(
nameResolver,
ProtoBuf.Package::getFunctionList,
ProtoBuf.Package::getPropertyList
)
return listOf(createChangeInfo(packageFqName, memberNames))
}
private fun computeClassChanges(nameResolver: NameResolver, classProto: ProtoBuf.Class, createChangeInfo: (FqName, Collection<String>) -> ChangeInfo): List<ChangeInfo> {
val classFqName = nameResolver.getClassId(classProto.fqName).asSingleFqName()
val kind = Flags.CLASS_KIND.get(classProto.flags)
return if (kind == ProtoBuf.Class.Kind.COMPANION_OBJECT) {
val memberNames =
classProto.getNonPrivateNames(
nameResolver,
ProtoBuf.Class::getConstructorList,
ProtoBuf.Class::getFunctionList,
ProtoBuf.Class::getPropertyList
) + classProto.enumEntryList.map { nameResolver.getString(it.name) }
val companionObjectChanged = createChangeInfo(classFqName.parent(), listOfNotNull(classFqName.shortName().asString()))
val companionObjectMembersChanged = createChangeInfo(classFqName, memberNames)
listOf(companionObjectMembersChanged, companionObjectChanged)
}
else {
listOf(ChangeInfo.SignatureChanged(classFqName, areSubclassesAffected = true))
}
}
fun clearCacheForRemovedClasses(): CompilationResult {
fun clearCacheForRemovedClasses(changesCollector: ChangesCollector) {
val dirtyClasses = dirtyOutputClassesMap
.getDirtyOutputClasses()
.map(JvmClassName::byInternalName)
.toList()
val changes = dirtyClasses.flatMap { computeChanges(it, ::Removed) }.asSequence()
val changesInfo = CompilationResult(changes = changes)
val facadesWithRemovedParts = hashMapOf<JvmClassName, MutableSet<String>>()
for (dirtyClass in dirtyClasses) {
val facade = partToMultifileFacade.get(dirtyClass.internalName) ?: continue
@@ -281,7 +196,7 @@ open class IncrementalCacheImpl(
}
dirtyClasses.forEach {
protoMap.remove(it)
protoMap.remove(it, changesCollector)
packagePartMap.remove(it)
multifileFacadeToParts.remove(it)
partToMultifileFacade.remove(it)
@@ -293,7 +208,6 @@ open class IncrementalCacheImpl(
removeAllFromClassStorage(dirtyClasses.map { it.fqNameForClassNameWithoutDollars })
dirtyOutputClassesMap.clean()
return changesInfo
}
override fun getObsoletePackageParts(): Collection<String> {
@@ -335,10 +249,8 @@ open class IncrementalCacheImpl(
private inner class ProtoMap(storageFile: File) : BasicStringMap<ProtoMapValue>(storageFile, ProtoMapValueExternalizer) {
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult {
val header = kotlinClass.classHeader
val bytes = BitEncoding.decodeBytes(header.data!!)
return put(kotlinClass.className, bytes, header.strings!!, isPackage)
fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
return put(kotlinClass, changesCollector)
}
// A module mapping (.kotlin_module file) is stored in a cache,
@@ -348,47 +260,22 @@ open class IncrementalCacheImpl(
// from files compiled during last round.
// However there is no need to compare old and new data in this case
// (also that would fail with exception).
fun storeModuleMapping(className: JvmClassName, bytes: ByteArray): CompilationResult {
fun storeModuleMapping(className: JvmClassName, bytes: ByteArray) {
storage[className.internalName] = ProtoMapValue(isPackageFacade = false, bytes = bytes, strings = emptyArray())
return CompilationResult()
}
private fun put(
className: JvmClassName,
bytes: ByteArray,
strings: Array<String>,
isPackage: Boolean
): CompilationResult {
val key = className.internalName
private fun put(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
val header = kotlinClass.classHeader
val key = kotlinClass.className.internalName
val oldData = storage[key]
val data = ProtoMapValue(isPackage, bytes, strings)
val newData = ProtoMapValue(header.kind != KotlinClassHeader.Kind.CLASS,
BitEncoding.decodeBytes(header.data!!),
header.strings!!)
storage[key] = newData
if (oldData == null ||
!Arrays.equals(bytes, oldData.bytes) ||
!Arrays.equals(strings, oldData.strings) ||
isPackage != oldData.isPackageFacade
) {
storage[key] = data
}
if (oldData == null) {
val changes = computeChanges(className, ::MembersChanged).asSequence()
return CompilationResult(changes = changes)
}
val difference = difference(oldData, data)
val fqName = if (isPackage) className.packageFqName else className.fqNameForClassNameWithoutDollars
val changeList = SmartList<ChangeInfo>()
if (difference.isClassAffected) {
changeList.add(ChangeInfo.SignatureChanged(fqName, difference.areSubclassesAffected))
}
if (difference.changedMembersNames.isNotEmpty()) {
changeList.add(ChangeInfo.MembersChanged(fqName, difference.changedMembersNames))
}
return CompilationResult(changes = changeList.asSequence())
val packageFqName = kotlinClass.className.packageFqName
changesCollector.collectProtoChanges(oldData?.toProtoData(packageFqName), newData.toProtoData(packageFqName))
}
operator fun contains(className: JvmClassName): Boolean =
@@ -397,8 +284,11 @@ open class IncrementalCacheImpl(
operator fun get(className: JvmClassName): ProtoMapValue? =
storage[className.internalName]
fun remove(className: JvmClassName) {
storage.remove(className.internalName)
fun remove(className: JvmClassName, changesCollector: ChangesCollector) {
val key = className.internalName
val oldValue = storage[key] ?: return
changesCollector.collectProtoChanges(oldData = oldValue.toProtoData(className.packageFqName), newData = null)
storage.remove(key)
}
override fun dumpValue(value: ProtoMapValue): String {
@@ -406,8 +296,9 @@ open class IncrementalCacheImpl(
}
}
// todo: reuse code with InlineFunctionsMap?
private inner class ConstantsMap(storageFile: File) : BasicStringMap<Map<String, Any>>(storageFile, ConstantsMapExternalizer) {
private fun getConstantsMap(bytes: ByteArray): Map<String, Any>? {
private fun getConstantsMap(bytes: ByteArray): Map<String, Any> {
val result = HashMap<String, Any>()
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5) {
@@ -420,45 +311,27 @@ open class IncrementalCacheImpl(
}
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
return if (result.isEmpty()) null else result
return result
}
operator fun contains(className: JvmClassName): Boolean =
className.internalName in storage
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult {
return put(kotlinClass.className, getConstantsMap(kotlinClass.fileContents), isPackage)
}
fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
val key = kotlinClass.className.internalName
val oldMap = storage[key] ?: emptyMap()
private fun put(className: JvmClassName, constantsMap: Map<String, Any>?, isPackage: Boolean): CompilationResult {
val key = className.internalName
val oldMap = storage[key]
if (oldMap == constantsMap) return CompilationResult.NO_CHANGES
if (constantsMap != null) {
storage[key] = constantsMap
val newMap = getConstantsMap(kotlinClass.fileContents)
if (newMap.isNotEmpty()) {
storage[key] = newMap
}
else {
remove(className)
storage.remove(key)
}
val changes =
if (constantsMap == null || constantsMap.isEmpty() ||
oldMap == null || oldMap.isEmpty()
) {
emptySequence<ChangeInfo>()
}
else {
// we need only changed constants everything other should be covered by diff
val changedNames = oldMap.filter { constantsMap.containsKey(it.key) && constantsMap[it.key] != it.value }.map { it.key }
val fqName = if (isPackage) className.packageFqName else className.fqNameForClassNameWithoutDollars
sequenceOf(ChangeInfo.MembersChanged(fqName, changedNames))
}
return CompilationResult(changes = changes)
for (const in oldMap.keys + newMap.keys) {
changesCollector.collectMemberIfValueWasChanged(kotlinClass.scopeFqName(), const, oldMap[const], newMap[const])
}
}
fun remove(className: JvmClassName) {
@@ -602,39 +475,27 @@ open class IncrementalCacheImpl(
return result
}
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult {
return put(kotlinClass.className, getInlineFunctionsMap(kotlinClass.classHeader, kotlinClass.fileContents), isPackage)
}
fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
val key = kotlinClass.className.internalName
val oldMap = storage[key] ?: emptyMap()
private fun put(className: JvmClassName, newMap: Map<String, Long>, isPackage: Boolean): CompilationResult {
val internalName = className.internalName
val oldMap = storage[internalName] ?: emptyMap()
val added = hashSetOf<String>()
val changed = hashSetOf<String>()
val allFunctions = oldMap.keys + newMap.keys
for (fn in allFunctions) {
val oldHash = oldMap[fn]
val newHash = newMap[fn]
when {
oldHash == null -> added.add(fn)
oldHash != newHash -> changed.add(fn)
}
val newMap = getInlineFunctionsMap(kotlinClass.classHeader, kotlinClass.fileContents)
if (newMap.isNotEmpty()) {
storage[key] = newMap
}
else {
storage.remove(key)
}
when {
newMap.isNotEmpty() -> storage[internalName] = newMap
else -> storage.remove(internalName)
for (fn in oldMap.keys + newMap.keys) {
changesCollector.collectMemberIfValueWasChanged(kotlinClass.scopeFqName(), functionNameBySignature(fn), oldMap[fn], newMap[fn])
}
val fqName = if (isPackage) className.packageFqName else className.fqNameForClassNameWithoutDollars
// TODO get name in better way instead of using substringBefore
val changes = (added.asSequence() + changed.asSequence()).map { ChangeInfo.MembersChanged(fqName, listOf(it.substringBefore("("))) }
return CompilationResult(changes = changes)
}
// TODO get name in better way instead of using substringBefore
private fun functionNameBySignature(signature: String): String =
signature.substringBefore("(")
fun remove(className: JvmClassName) {
storage.remove(className.internalName)
}
@@ -663,16 +524,11 @@ sealed class ChangeInfo(val fqName: FqName) {
}
}
data class CompilationResult(
val changes: Sequence<ChangeInfo> = emptySequence()
) {
companion object {
val NO_CHANGES: CompilationResult = CompilationResult()
}
operator fun plus(other: CompilationResult): CompilationResult =
CompilationResult(changes + other.changes)
}
private fun LocalFileKotlinClass.scopeFqName() =
when (classHeader.kind) {
KotlinClassHeader.Kind.CLASS -> className.fqNameForClassNameWithoutDollars
else -> className.packageFqName
}
fun ByteArray.md5(): Long {
val d = MessageDigest.getInstance("MD5").digest(this)!!
@@ -78,20 +78,17 @@ fun makeCompileServices(
fun updateIncrementalCache(
generatedFiles: Iterable<GeneratedFile>,
cache: IncrementalCacheImpl
): CompilationResult {
var changesInfo = CompilationResult.NO_CHANGES
cache: IncrementalCacheImpl,
changesCollector: ChangesCollector
) {
for (generatedFile in generatedFiles) {
when {
generatedFile is GeneratedJvmClass -> changesInfo += cache.saveFileToCache(generatedFile)
generatedFile.outputFile.isModuleMappingFile() -> changesInfo += cache.saveModuleMappingToCache(generatedFile.sourceFiles, generatedFile.outputFile)
generatedFile is GeneratedJvmClass -> cache.saveFileToCache(generatedFile, changesCollector)
generatedFile.outputFile.isModuleMappingFile() -> cache.saveModuleMappingToCache(generatedFile.sourceFiles, generatedFile.outputFile)
}
}
val newChangesInfo = cache.clearCacheForRemovedClasses()
changesInfo += newChangesInfo
return changesInfo
cache.clearCacheForRemovedClasses(changesCollector)
}
fun LookupStorage.update(
@@ -111,14 +108,14 @@ data class DirtyData(
val dirtyClassesFqNames: Collection<FqName> = emptyList()
)
fun CompilationResult.getDirtyData(
fun ChangesCollector.getDirtyData(
caches: Iterable<IncrementalCacheCommon>,
reporter: ICReporter
): DirtyData {
val dirtyLookupSymbols = HashSet<LookupSymbol>()
val dirtyClassesFqNames = HashSet<FqName>()
for (change in changes) {
for (change in changes()) {
reporter.report { "Process $change" }
if (change is ChangeInfo.SignatureChanged) {
@@ -16,11 +16,11 @@
package org.jetbrains.kotlin.incremental
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.incremental.ProtoCompareGenerated.ProtoBufClassKind
import org.jetbrains.kotlin.incremental.ProtoCompareGenerated.ProtoBufPackageKind
import org.jetbrains.kotlin.incremental.storage.ProtoMapValue
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.protobuf.MessageLite
import org.jetbrains.kotlin.serialization.Flags
import org.jetbrains.kotlin.serialization.ProtoBuf
@@ -32,48 +32,23 @@ import java.util.*
data class Difference(
val isClassAffected: Boolean = false,
val areSubclassesAffected: Boolean = false,
val changedMembersNames: Set<String> = emptySet(),
@TestOnly
val rawProtoDifference: EnumSet<*>? = null
val changedMembersNames: Set<String> = emptySet()
)
sealed class ProtoData
data class ClassProtoData(val proto: ProtoBuf.Class, val nameResolver: NameResolver) : ProtoData()
data class PackagePartProtoData(val proto: ProtoBuf.Package, val nameResolver: NameResolver) : ProtoData()
data class PackagePartProtoData(val proto: ProtoBuf.Package, val nameResolver: NameResolver, val packageFqName: FqName) : ProtoData()
fun ProtoMapValue.toProtoData(): ProtoData =
fun ProtoMapValue.toProtoData(packageFqName: FqName): ProtoData =
if (isPackageFacade) {
val packageData = JvmProtoBufUtil.readPackageDataFrom(bytes, strings)
PackagePartProtoData(packageData.packageProto, packageData.nameResolver)
PackagePartProtoData(packageData.packageProto, packageData.nameResolver, packageFqName)
}
else {
val classData = JvmProtoBufUtil.readClassDataFrom(bytes, strings)
ClassProtoData(classData.classProto, classData.nameResolver)
}
fun difference(oldValue: ProtoMapValue, newValue: ProtoMapValue): Difference =
difference(oldValue.toProtoData(), newValue.toProtoData())
fun difference(oldData: ProtoData, newData: ProtoData): Difference =
when (oldData) {
is ClassProtoData -> {
when (newData) {
is ClassProtoData ->
DifferenceCalculatorForClass(oldData, newData).difference()
is PackagePartProtoData ->
Difference(isClassAffected = true, areSubclassesAffected = true)
}
}
is PackagePartProtoData -> {
when (newData) {
is ClassProtoData ->
Difference(isClassAffected = true)
is PackagePartProtoData ->
DifferenceCalculatorForPackageFacade(oldData, newData).difference()
}
}
}
internal val MessageLite.isPrivate: Boolean
get() = Visibilities.isPrivate(Deserialization.visibility(
when (this) {
@@ -96,7 +71,7 @@ private fun MessageLite.name(nameResolver: NameResolver): String {
internal fun List<MessageLite>.names(nameResolver: NameResolver): List<String> = map { it.name(nameResolver) }
private abstract class DifferenceCalculator {
abstract class DifferenceCalculator {
protected abstract val compareObject: ProtoCompareGenerated
abstract fun difference(): Difference
@@ -179,7 +154,7 @@ private abstract class DifferenceCalculator {
}
}
private class DifferenceCalculatorForClass(
class DifferenceCalculatorForClass(
private val oldData: ClassProtoData,
private val newData: ClassProtoData
) : DifferenceCalculator() {
@@ -272,11 +247,11 @@ private class DifferenceCalculatorForClass(
}
}
return Difference(isClassAffected, areSubclassesAffected, names, rawProtoDifference = diff)
return Difference(isClassAffected, areSubclassesAffected, names)
}
}
private class DifferenceCalculatorForPackageFacade(
class DifferenceCalculatorForPackageFacade(
private val oldData: PackagePartProtoData,
private val newData: PackagePartProtoData
) : DifferenceCalculator() {
@@ -317,7 +292,7 @@ private class DifferenceCalculatorForPackageFacade(
}
}
return Difference(changedMembersNames = names, rawProtoDifference = diff)
return Difference(changedMembersNames = names)
}
}
@@ -135,7 +135,12 @@ abstract class IncrementalCompilerRunner<
caches.platformCache.markDirty(dirtySources)
}
protected abstract fun compareAndUpdateCache(services: Services, caches: CacheManager, generatedFiles: List<GeneratedFile>): CompilationResult
protected abstract fun updateCaches(
services: Services,
caches: CacheManager,
generatedFiles: List<GeneratedFile>,
changesCollector: ChangesCollector
)
protected open fun preBuildHook(args: Args, compilationMode: CompilationMode) {}
protected open fun postCompilationHook(exitCode: ExitCode) {}
@@ -226,11 +231,12 @@ abstract class IncrementalCompilerRunner<
allGeneratedFiles.addAll(generatedFiles)
caches.inputsCache.registerOutputForSourceFiles(generatedFiles)
caches.lookupCache.update(lookupTracker, sourcesToCompile, removedKotlinSources)
val compilationResult = compareAndUpdateCache(services, caches, generatedFiles)
val changesCollector = ChangesCollector()
updateCaches(services, caches, generatedFiles, changesCollector)
if (compilationMode is CompilationMode.Rebuild) break
val (dirtyLookupSymbols, dirtyClassFqNames) = compilationResult.getDirtyData(listOf(caches.platformCache), reporter)
val (dirtyLookupSymbols, dirtyClassFqNames) = changesCollector.getDirtyData(listOf(caches.platformCache), reporter)
val compiledInThisIterationSet = sourcesToCompile.toHashSet()
with (dirtySources) {
@@ -211,8 +211,14 @@ class IncrementalJvmCompilerRunner(
}
}
override fun compareAndUpdateCache(services: Services, caches: IncrementalJvmCachesManager, generatedFiles: List<GeneratedFile>): CompilationResult =
updateIncrementalCache(generatedFiles, caches.platformCache)
override fun updateCaches(
services: Services,
caches: IncrementalJvmCachesManager,
generatedFiles: List<GeneratedFile>,
changesCollector: ChangesCollector
) {
updateIncrementalCache(generatedFiles, caches.platformCache, changesCollector)
}
override fun additionalDirtyFiles(
caches: IncrementalJvmCachesManager,
@@ -21,11 +21,8 @@ import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants
import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.ClassProtoData
import org.jetbrains.kotlin.incremental.Difference
import org.jetbrains.kotlin.incremental.difference as differenceImpl
import org.jetbrains.kotlin.incremental.PackagePartProtoData
import org.jetbrains.kotlin.incremental.ProtoData
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.utils.TestMessageCollector
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumerImpl
@@ -72,28 +69,33 @@ abstract class AbstractJsProtoComparisonTest : AbstractProtoComparisonTest<Proto
val classes = hashMapOf<ClassId, ProtoData>()
for ((sourceFile, protoBytes, _) in incrementalResults.packageParts) {
val proto = ProtoBuf.PackageFragment.parseFrom(protoBytes, JsSerializerProtocol.extensionRegistry)
val nameResolver = NameResolverImpl(proto.strings, proto.qualifiedNames)
proto.class_List.forEach {
val classId = nameResolver.getClassId(it.fqName)
classes[classId] = ClassProtoData(it, nameResolver)
}
proto.`package`.apply {
val packageFqName = if (hasExtension(JsProtoBuf.packageFqName)) {
nameResolver.getPackageFqName(getExtension(JsProtoBuf.packageFqName))
}
else FqName.ROOT
val packagePartClassId = ClassId(packageFqName, Name.identifier(sourceFile.nameWithoutExtension.capitalize() + "Kt"))
classes[packagePartClassId] = PackagePartProtoData(this, nameResolver)
}
classes.putAll(getProtoData(sourceFile, protoBytes))
}
return classes
}
override fun difference(oldData: ProtoData, newData: ProtoData): Difference? =
differenceImpl(oldData, newData)
override fun ProtoData.toProtoData(): ProtoData? = this
}
fun getProtoData(sourceFile: File, metadata: ByteArray): Map<ClassId, ProtoData> {
val classes = hashMapOf<ClassId, ProtoData>()
val proto = ProtoBuf.PackageFragment.parseFrom(metadata, JsSerializerProtocol.extensionRegistry)
val nameResolver = NameResolverImpl(proto.strings, proto.qualifiedNames)
proto.class_List.forEach {
val classId = nameResolver.getClassId(it.fqName)
classes[classId] = ClassProtoData(it, nameResolver)
}
proto.`package`.apply {
val packageFqName = if (hasExtension(JsProtoBuf.packageFqName)) {
nameResolver.getPackageFqName(getExtension(JsProtoBuf.packageFqName))
}
else FqName.ROOT
val packagePartClassId = ClassId(packageFqName, Name.identifier(sourceFile.nameWithoutExtension.capitalize() + "Kt"))
classes[packagePartClassId] = PackagePartProtoData(this, nameResolver, packageFqName)
}
return classes
}
@@ -16,9 +16,7 @@
package org.jetbrains.kotlin.jps.incremental
import org.jetbrains.kotlin.incremental.Difference
import org.jetbrains.kotlin.incremental.LocalFileKotlinClass
import org.jetbrains.kotlin.incremental.difference
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.storage.ProtoMapValue
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -36,25 +34,20 @@ abstract class AbstractJvmProtoComparisonTest : AbstractProtoComparisonTest<Loca
return localClassFiles.associateBy { it.classId }
}
override fun difference(oldData: LocalFileKotlinClass, newData: LocalFileKotlinClass): Difference? {
val oldProto = oldData.readProto() ?: return null
val newProto = newData.readProto() ?: return null
return difference(oldProto, newProto)
}
private fun KotlinJvmBinaryClass.readProto(): ProtoMapValue? {
override fun LocalFileKotlinClass.toProtoData(): ProtoData? {
assert(classHeader.metadataVersion.isCompatible()) { "Incompatible class ($classHeader): $location" }
val bytes by lazy { BitEncoding.decodeBytes(classHeader.data!!) }
val strings by lazy { classHeader.strings!! }
val packageFqName = classId.packageFqName
return when (classHeader.kind) {
KotlinClassHeader.Kind.CLASS -> {
ProtoMapValue(false, bytes, strings)
ProtoMapValue(false, bytes, strings).toProtoData(packageFqName)
}
KotlinClassHeader.Kind.FILE_FACADE,
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
ProtoMapValue(true, bytes, strings)
ProtoMapValue(true, bytes, strings).toProtoData(packageFqName)
}
else -> {
null
@@ -17,7 +17,7 @@
package org.jetbrains.kotlin.jps.incremental
import org.jetbrains.kotlin.TestWithWorkingDir
import org.jetbrains.kotlin.incremental.Difference
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.utils.Printer
@@ -25,7 +25,7 @@ import java.io.File
abstract class AbstractProtoComparisonTest<PROTO_DATA> : TestWithWorkingDir() {
protected abstract fun compileAndGetClasses(sourceDir: File, outputDir: File): Map<ClassId, PROTO_DATA>
protected abstract fun difference(oldData: PROTO_DATA, newData: PROTO_DATA): Difference?
protected abstract fun PROTO_DATA.toProtoData(): ProtoData?
protected open fun expectedOutputFile(testDir: File): File =
File(testDir, "result.out")
@@ -48,32 +48,41 @@ abstract class AbstractProtoComparisonTest<PROTO_DATA> : TestWithWorkingDir() {
}
(oldClassMap.keys.intersect(newClassMap.keys)).sortedBy { it.toString() }.forEach { classId ->
val oldData = oldClassMap[classId]!!
val newData = newClassMap[classId]!!
val diff = difference(oldData, newData)
val oldData = oldClassMap[classId]!!.toProtoData()
val newData = newClassMap[classId]!!.toProtoData()
if (diff == null) {
if (oldData == null || newData == null) {
p.println("SKIPPED $classId")
return@forEach
}
diff.rawProtoDifference?.let {
val rawProtoDifference = when {
oldData is ClassProtoData && newData is ClassProtoData -> {
ProtoCompareGenerated(oldData.nameResolver, newData.nameResolver).difference(oldData.proto, newData.proto)
}
oldData is PackagePartProtoData && newData is PackagePartProtoData -> {
ProtoCompareGenerated(oldData.nameResolver, newData.nameResolver).difference(oldData.proto, newData.proto)
}
else -> null
}
rawProtoDifference?.let {
if (it.isNotEmpty()) {
p.println("PROTO DIFFERENCE in $classId: ${it.sortedBy { it.name }.joinToString()}")
}
}
val changes = arrayListOf<String>()
if (diff.isClassAffected) {
changes.add("CLASS_SIGNATURE")
}
if (diff.changedMembersNames.isNotEmpty()) {
changes.add("MEMBERS\n ${diff.changedMembersNames.sorted()}")
}
if (changes.isEmpty()) {
val changesInfo = ChangesCollector().apply { collectProtoChanges(oldData, newData) }.changes()
if (changesInfo.isEmpty()) {
return@forEach
}
val changes = changesInfo.map {
when (it) {
is ChangeInfo.SignatureChanged -> "CLASS_SIGNATURE"
is ChangeInfo.MembersChanged -> "MEMBERS\n ${it.names.sorted()}"
}
}.sorted()
p.println("CHANGES in $classId: ${changes.joinToString()}")
}
@@ -314,8 +314,9 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
context.checkCanceled()
val changesInfo = generatedFiles.entries.fold(CompilationResult.NO_CHANGES) { acc, (target, files) ->
acc + updateIncrementalCache(files, incrementalCaches[target]!!)
val changesCollector = ChangesCollector()
for ((target, files) in generatedFiles) {
updateIncrementalCache(files, incrementalCaches[target]!!, changesCollector)
}
updateLookupStorage(chunk, lookupTracker, dataManager, dirtyFilesHolder, filesToCompile)
@@ -323,7 +324,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
return OK
}
changesInfo.processChangesUsingLookups(filesToCompile.values().toSet(), dataManager, fsOperations, incrementalCaches.values)
changesCollector.processChangesUsingLookups(filesToCompile.values().toSet(), dataManager, fsOperations, incrementalCaches.values)
return OK
}
@@ -432,7 +433,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
for (target in chunk.targets) {
val cache = incrementalCaches[target]!!
val removedAndDirtyFiles = filesToCompile[target] + dirtyFilesHolder.getRemovedFiles(target).map(::File)
cache.markOutputClassesDirty(removedAndDirtyFiles)
cache.markDirty(removedAndDirtyFiles)
}
}
@@ -774,7 +775,7 @@ private class JpsICReporter : ICReporter {
}
}
private fun CompilationResult.processChangesUsingLookups(
private fun ChangesCollector.processChangesUsingLookups(
compiledFiles: Set<File>,
dataManager: BuildDataManager,
fsOperations: FSOperationsHelper,