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
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
import com.intellij.util.SmartList
import com.intellij.util.io.BooleanDataDescriptor import com.intellij.util.io.BooleanDataDescriptor
import com.intellij.util.io.EnumeratorStringDescriptor import com.intellij.util.io.EnumeratorStringDescriptor
import gnu.trove.THashSet import gnu.trove.THashSet
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.build.GeneratedJvmClass 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.incremental.storage.*
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
import org.jetbrains.kotlin.load.kotlin.ModuleMapping 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.IncrementalCache
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.protobuf.MessageLite
import org.jetbrains.kotlin.resolve.jvm.JvmClassName 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.BitEncoding
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.*
@@ -106,15 +99,14 @@ open class IncrementalCacheImpl(
return toSystemIndependentName(File(outputDir, "$internalClassName.class").canonicalPath) 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) val jvmClassName = JvmClassName.byInternalName(MODULE_MAPPING_FILE_NAME)
protoMap.storeModuleMapping(jvmClassName, file.readBytes()) protoMap.storeModuleMapping(jvmClassName, file.readBytes())
dirtyOutputClassesMap.notDirty(MODULE_MAPPING_FILE_NAME) dirtyOutputClassesMap.notDirty(MODULE_MAPPING_FILE_NAME)
sourceFiles.forEach { sourceToClassesMap.add(it, jvmClassName) } 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 sourceFiles: Collection<File> = generatedClass.sourceFiles
val kotlinClass: LocalFileKotlinClass = generatedClass.outputClass val kotlinClass: LocalFileKotlinClass = generatedClass.outputClass
val className = kotlinClass.className val className = kotlinClass.className
@@ -126,19 +118,17 @@ open class IncrementalCacheImpl(
internalNameToSource[className.internalName] = sourceFiles internalNameToSource[className.internalName] = sourceFiles
if (kotlinClass.classId.isLocal) { if (kotlinClass.classId.isLocal) return
return CompilationResult.NO_CHANGES
}
val header = kotlinClass.classHeader val header = kotlinClass.classHeader
val changesInfo = when (header.kind) { when (header.kind) {
KotlinClassHeader.Kind.FILE_FACADE -> { KotlinClassHeader.Kind.FILE_FACADE -> {
assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" } assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className) packagePartMap.addPackagePart(className)
protoMap.process(kotlinClass, isPackage = true) + protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, isPackage = true) + constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, isPackage = true) inlineFunctionsMap.process(kotlinClass, changesCollector)
} }
KotlinClassHeader.Kind.MULTIFILE_CLASS -> { KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
val partNames = kotlinClass.classHeader.data?.toList() val partNames = kotlinClass.classHeader.data?.toList()
@@ -148,118 +138,43 @@ open class IncrementalCacheImpl(
// the class' proto wouldn't ever be deleted, // the class' proto wouldn't ever be deleted,
// because we don't write proto for multifile facades. // because we don't write proto for multifile facades.
// As a workaround we can remove proto values for multifile facades. // As a workaround we can remove proto values for multifile facades.
val additionalChangeInfo = if (className in protoMap) { if (className in protoMap) {
val info = ChangeInfo.SignatureChanged(className.fqNameForClassNameWithoutDollars, areSubclassesAffected = true) changesCollector.collectSignature(className.fqNameForClassNameWithoutDollars, areSubclassesAffected = true)
CompilationResult(changes = sequenceOf(info))
} }
else CompilationResult.NO_CHANGES protoMap.remove(className, changesCollector)
protoMap.remove(className)
classFqNameToSourceMap.remove(className.fqNameForClassNameWithoutDollars) classFqNameToSourceMap.remove(className.fqNameForClassNameWithoutDollars)
internalNameToSource.remove(className.internalName) internalNameToSource.remove(className.internalName)
// TODO NO_CHANGES? (delegates only) // TODO NO_CHANGES? (delegates only)
constantsMap.process(kotlinClass, isPackage = true) + constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, isPackage = true) + inlineFunctionsMap.process(kotlinClass, changesCollector)
additionalChangeInfo
} }
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> { KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" } assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" }
packagePartMap.addPackagePart(className) packagePartMap.addPackagePart(className)
partToMultifileFacade.set(className.internalName, header.multifileClassName!!) partToMultifileFacade.set(className.internalName, header.multifileClassName!!)
protoMap.process(kotlinClass, isPackage = true) + protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, isPackage = true) + constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, isPackage = true) inlineFunctionsMap.process(kotlinClass, changesCollector)
} }
KotlinClassHeader.Kind.CLASS -> { KotlinClassHeader.Kind.CLASS -> {
assert(sourceFiles.size == 1) { "Class is expected to have only one source file: $sourceFiles" } assert(sourceFiles.size == 1) { "Class is expected to have only one source file: $sourceFiles" }
addToClassStorage(kotlinClass, sourceFiles.first()) addToClassStorage(kotlinClass, sourceFiles.first())
protoMap.process(kotlinClass, isPackage = false) + protoMap.process(kotlinClass, changesCollector)
constantsMap.process(kotlinClass, isPackage = false) + constantsMap.process(kotlinClass, changesCollector)
inlineFunctionsMap.process(kotlinClass, isPackage = false) inlineFunctionsMap.process(kotlinClass, changesCollector)
}
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)
} }
} }
} }
private fun <T> T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List<MessageLite>): Set<String> = fun clearCacheForRemovedClasses(changesCollector: ChangesCollector) {
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 {
val dirtyClasses = dirtyOutputClassesMap val dirtyClasses = dirtyOutputClassesMap
.getDirtyOutputClasses() .getDirtyOutputClasses()
.map(JvmClassName::byInternalName) .map(JvmClassName::byInternalName)
.toList() .toList()
val changes = dirtyClasses.flatMap { computeChanges(it, ::Removed) }.asSequence()
val changesInfo = CompilationResult(changes = changes)
val facadesWithRemovedParts = hashMapOf<JvmClassName, MutableSet<String>>() val facadesWithRemovedParts = hashMapOf<JvmClassName, MutableSet<String>>()
for (dirtyClass in dirtyClasses) { for (dirtyClass in dirtyClasses) {
val facade = partToMultifileFacade.get(dirtyClass.internalName) ?: continue val facade = partToMultifileFacade.get(dirtyClass.internalName) ?: continue
@@ -281,7 +196,7 @@ open class IncrementalCacheImpl(
} }
dirtyClasses.forEach { dirtyClasses.forEach {
protoMap.remove(it) protoMap.remove(it, changesCollector)
packagePartMap.remove(it) packagePartMap.remove(it)
multifileFacadeToParts.remove(it) multifileFacadeToParts.remove(it)
partToMultifileFacade.remove(it) partToMultifileFacade.remove(it)
@@ -293,7 +208,6 @@ open class IncrementalCacheImpl(
removeAllFromClassStorage(dirtyClasses.map { it.fqNameForClassNameWithoutDollars }) removeAllFromClassStorage(dirtyClasses.map { it.fqNameForClassNameWithoutDollars })
dirtyOutputClassesMap.clean() dirtyOutputClassesMap.clean()
return changesInfo
} }
override fun getObsoletePackageParts(): Collection<String> { override fun getObsoletePackageParts(): Collection<String> {
@@ -335,10 +249,8 @@ open class IncrementalCacheImpl(
private inner class ProtoMap(storageFile: File) : BasicStringMap<ProtoMapValue>(storageFile, ProtoMapValueExternalizer) { private inner class ProtoMap(storageFile: File) : BasicStringMap<ProtoMapValue>(storageFile, ProtoMapValueExternalizer) {
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult { fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
val header = kotlinClass.classHeader return put(kotlinClass, changesCollector)
val bytes = BitEncoding.decodeBytes(header.data!!)
return put(kotlinClass.className, bytes, header.strings!!, isPackage)
} }
// A module mapping (.kotlin_module file) is stored in a cache, // A module mapping (.kotlin_module file) is stored in a cache,
@@ -348,47 +260,22 @@ open class IncrementalCacheImpl(
// from files compiled during last round. // from files compiled during last round.
// However there is no need to compare old and new data in this case // However there is no need to compare old and new data in this case
// (also that would fail with exception). // (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()) storage[className.internalName] = ProtoMapValue(isPackageFacade = false, bytes = bytes, strings = emptyArray())
return CompilationResult()
} }
private fun put( private fun put(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
className: JvmClassName, val header = kotlinClass.classHeader
bytes: ByteArray,
strings: Array<String>, val key = kotlinClass.className.internalName
isPackage: Boolean
): CompilationResult {
val key = className.internalName
val oldData = storage[key] 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 || val packageFqName = kotlinClass.className.packageFqName
!Arrays.equals(bytes, oldData.bytes) || changesCollector.collectProtoChanges(oldData?.toProtoData(packageFqName), newData.toProtoData(packageFqName))
!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())
} }
operator fun contains(className: JvmClassName): Boolean = operator fun contains(className: JvmClassName): Boolean =
@@ -397,8 +284,11 @@ open class IncrementalCacheImpl(
operator fun get(className: JvmClassName): ProtoMapValue? = operator fun get(className: JvmClassName): ProtoMapValue? =
storage[className.internalName] storage[className.internalName]
fun remove(className: JvmClassName) { fun remove(className: JvmClassName, changesCollector: ChangesCollector) {
storage.remove(className.internalName) 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 { 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 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>() val result = HashMap<String, Any>()
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5) { 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) }, 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 = operator fun contains(className: JvmClassName): Boolean =
className.internalName in storage className.internalName in storage
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult { fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
return put(kotlinClass.className, getConstantsMap(kotlinClass.fileContents), isPackage) val key = kotlinClass.className.internalName
} val oldMap = storage[key] ?: emptyMap()
private fun put(className: JvmClassName, constantsMap: Map<String, Any>?, isPackage: Boolean): CompilationResult { val newMap = getConstantsMap(kotlinClass.fileContents)
val key = className.internalName if (newMap.isNotEmpty()) {
storage[key] = newMap
val oldMap = storage[key]
if (oldMap == constantsMap) return CompilationResult.NO_CHANGES
if (constantsMap != null) {
storage[key] = constantsMap
} }
else { else {
remove(className) storage.remove(key)
} }
val changes = for (const in oldMap.keys + newMap.keys) {
if (constantsMap == null || constantsMap.isEmpty() || changesCollector.collectMemberIfValueWasChanged(kotlinClass.scopeFqName(), const, oldMap[const], newMap[const])
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)
} }
fun remove(className: JvmClassName) { fun remove(className: JvmClassName) {
@@ -602,39 +475,27 @@ open class IncrementalCacheImpl(
return result return result
} }
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult { fun process(kotlinClass: LocalFileKotlinClass, changesCollector: ChangesCollector) {
return put(kotlinClass.className, getInlineFunctionsMap(kotlinClass.classHeader, kotlinClass.fileContents), isPackage) val key = kotlinClass.className.internalName
} val oldMap = storage[key] ?: emptyMap()
private fun put(className: JvmClassName, newMap: Map<String, Long>, isPackage: Boolean): CompilationResult { val newMap = getInlineFunctionsMap(kotlinClass.classHeader, kotlinClass.fileContents)
val internalName = className.internalName if (newMap.isNotEmpty()) {
val oldMap = storage[internalName] ?: emptyMap() storage[key] = newMap
}
val added = hashSetOf<String>() else {
val changed = hashSetOf<String>() storage.remove(key)
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)
}
} }
when { for (fn in oldMap.keys + newMap.keys) {
newMap.isNotEmpty() -> storage[internalName] = newMap changesCollector.collectMemberIfValueWasChanged(kotlinClass.scopeFqName(), functionNameBySignature(fn), oldMap[fn], newMap[fn])
else -> storage.remove(internalName)
} }
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) { fun remove(className: JvmClassName) {
storage.remove(className.internalName) storage.remove(className.internalName)
} }
@@ -663,16 +524,11 @@ sealed class ChangeInfo(val fqName: FqName) {
} }
} }
data class CompilationResult( private fun LocalFileKotlinClass.scopeFqName() =
val changes: Sequence<ChangeInfo> = emptySequence() when (classHeader.kind) {
) { KotlinClassHeader.Kind.CLASS -> className.fqNameForClassNameWithoutDollars
companion object { else -> className.packageFqName
val NO_CHANGES: CompilationResult = CompilationResult() }
}
operator fun plus(other: CompilationResult): CompilationResult =
CompilationResult(changes + other.changes)
}
fun ByteArray.md5(): Long { fun ByteArray.md5(): Long {
val d = MessageDigest.getInstance("MD5").digest(this)!! val d = MessageDigest.getInstance("MD5").digest(this)!!
@@ -78,20 +78,17 @@ fun makeCompileServices(
fun updateIncrementalCache( fun updateIncrementalCache(
generatedFiles: Iterable<GeneratedFile>, generatedFiles: Iterable<GeneratedFile>,
cache: IncrementalCacheImpl cache: IncrementalCacheImpl,
): CompilationResult { changesCollector: ChangesCollector
var changesInfo = CompilationResult.NO_CHANGES ) {
for (generatedFile in generatedFiles) { for (generatedFile in generatedFiles) {
when { when {
generatedFile is GeneratedJvmClass -> changesInfo += cache.saveFileToCache(generatedFile) generatedFile is GeneratedJvmClass -> cache.saveFileToCache(generatedFile, changesCollector)
generatedFile.outputFile.isModuleMappingFile() -> changesInfo += cache.saveModuleMappingToCache(generatedFile.sourceFiles, generatedFile.outputFile) generatedFile.outputFile.isModuleMappingFile() -> cache.saveModuleMappingToCache(generatedFile.sourceFiles, generatedFile.outputFile)
} }
} }
val newChangesInfo = cache.clearCacheForRemovedClasses() cache.clearCacheForRemovedClasses(changesCollector)
changesInfo += newChangesInfo
return changesInfo
} }
fun LookupStorage.update( fun LookupStorage.update(
@@ -111,14 +108,14 @@ data class DirtyData(
val dirtyClassesFqNames: Collection<FqName> = emptyList() val dirtyClassesFqNames: Collection<FqName> = emptyList()
) )
fun CompilationResult.getDirtyData( fun ChangesCollector.getDirtyData(
caches: Iterable<IncrementalCacheCommon>, caches: Iterable<IncrementalCacheCommon>,
reporter: ICReporter reporter: ICReporter
): DirtyData { ): DirtyData {
val dirtyLookupSymbols = HashSet<LookupSymbol>() val dirtyLookupSymbols = HashSet<LookupSymbol>()
val dirtyClassesFqNames = HashSet<FqName>() val dirtyClassesFqNames = HashSet<FqName>()
for (change in changes) { for (change in changes()) {
reporter.report { "Process $change" } reporter.report { "Process $change" }
if (change is ChangeInfo.SignatureChanged) { if (change is ChangeInfo.SignatureChanged) {
@@ -16,11 +16,11 @@
package org.jetbrains.kotlin.incremental package org.jetbrains.kotlin.incremental
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.incremental.ProtoCompareGenerated.ProtoBufClassKind import org.jetbrains.kotlin.incremental.ProtoCompareGenerated.ProtoBufClassKind
import org.jetbrains.kotlin.incremental.ProtoCompareGenerated.ProtoBufPackageKind import org.jetbrains.kotlin.incremental.ProtoCompareGenerated.ProtoBufPackageKind
import org.jetbrains.kotlin.incremental.storage.ProtoMapValue import org.jetbrains.kotlin.incremental.storage.ProtoMapValue
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.protobuf.MessageLite import org.jetbrains.kotlin.protobuf.MessageLite
import org.jetbrains.kotlin.serialization.Flags import org.jetbrains.kotlin.serialization.Flags
import org.jetbrains.kotlin.serialization.ProtoBuf import org.jetbrains.kotlin.serialization.ProtoBuf
@@ -32,48 +32,23 @@ import java.util.*
data class Difference( data class Difference(
val isClassAffected: Boolean = false, val isClassAffected: Boolean = false,
val areSubclassesAffected: Boolean = false, val areSubclassesAffected: Boolean = false,
val changedMembersNames: Set<String> = emptySet(), val changedMembersNames: Set<String> = emptySet()
@TestOnly
val rawProtoDifference: EnumSet<*>? = null
) )
sealed class ProtoData sealed class ProtoData
data class ClassProtoData(val proto: ProtoBuf.Class, val nameResolver: NameResolver) : 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) { if (isPackageFacade) {
val packageData = JvmProtoBufUtil.readPackageDataFrom(bytes, strings) val packageData = JvmProtoBufUtil.readPackageDataFrom(bytes, strings)
PackagePartProtoData(packageData.packageProto, packageData.nameResolver) PackagePartProtoData(packageData.packageProto, packageData.nameResolver, packageFqName)
} }
else { else {
val classData = JvmProtoBufUtil.readClassDataFrom(bytes, strings) val classData = JvmProtoBufUtil.readClassDataFrom(bytes, strings)
ClassProtoData(classData.classProto, classData.nameResolver) 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 internal val MessageLite.isPrivate: Boolean
get() = Visibilities.isPrivate(Deserialization.visibility( get() = Visibilities.isPrivate(Deserialization.visibility(
when (this) { 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) } 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 protected abstract val compareObject: ProtoCompareGenerated
abstract fun difference(): Difference abstract fun difference(): Difference
@@ -179,7 +154,7 @@ private abstract class DifferenceCalculator {
} }
} }
private class DifferenceCalculatorForClass( class DifferenceCalculatorForClass(
private val oldData: ClassProtoData, private val oldData: ClassProtoData,
private val newData: ClassProtoData private val newData: ClassProtoData
) : DifferenceCalculator() { ) : 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 oldData: PackagePartProtoData,
private val newData: PackagePartProtoData private val newData: PackagePartProtoData
) : DifferenceCalculator() { ) : 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) 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 preBuildHook(args: Args, compilationMode: CompilationMode) {}
protected open fun postCompilationHook(exitCode: ExitCode) {} protected open fun postCompilationHook(exitCode: ExitCode) {}
@@ -226,11 +231,12 @@ abstract class IncrementalCompilerRunner<
allGeneratedFiles.addAll(generatedFiles) allGeneratedFiles.addAll(generatedFiles)
caches.inputsCache.registerOutputForSourceFiles(generatedFiles) caches.inputsCache.registerOutputForSourceFiles(generatedFiles)
caches.lookupCache.update(lookupTracker, sourcesToCompile, removedKotlinSources) 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 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() val compiledInThisIterationSet = sourcesToCompile.toHashSet()
with (dirtySources) { with (dirtySources) {
@@ -211,8 +211,14 @@ class IncrementalJvmCompilerRunner(
} }
} }
override fun compareAndUpdateCache(services: Services, caches: IncrementalJvmCachesManager, generatedFiles: List<GeneratedFile>): CompilationResult = override fun updateCaches(
updateIncrementalCache(generatedFiles, caches.platformCache) services: Services,
caches: IncrementalJvmCachesManager,
generatedFiles: List<GeneratedFile>,
changesCollector: ChangesCollector
) {
updateIncrementalCache(generatedFiles, caches.platformCache, changesCollector)
}
override fun additionalDirtyFiles( override fun additionalDirtyFiles(
caches: IncrementalJvmCachesManager, 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.cli.js.K2JSCompiler
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.config.Services 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.ProtoData
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.utils.TestMessageCollector import org.jetbrains.kotlin.incremental.utils.TestMessageCollector
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumerImpl import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumerImpl
@@ -72,28 +69,33 @@ abstract class AbstractJsProtoComparisonTest : AbstractProtoComparisonTest<Proto
val classes = hashMapOf<ClassId, ProtoData>() val classes = hashMapOf<ClassId, ProtoData>()
for ((sourceFile, protoBytes, _) in incrementalResults.packageParts) { for ((sourceFile, protoBytes, _) in incrementalResults.packageParts) {
val proto = ProtoBuf.PackageFragment.parseFrom(protoBytes, JsSerializerProtocol.extensionRegistry) classes.putAll(getProtoData(sourceFile, protoBytes))
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)
}
} }
return classes return classes
} }
override fun difference(oldData: ProtoData, newData: ProtoData): Difference? = override fun ProtoData.toProtoData(): ProtoData? = this
differenceImpl(oldData, newData) }
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 package org.jetbrains.kotlin.jps.incremental
import org.jetbrains.kotlin.incremental.Difference import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.LocalFileKotlinClass
import org.jetbrains.kotlin.incremental.difference
import org.jetbrains.kotlin.incremental.storage.ProtoMapValue import org.jetbrains.kotlin.incremental.storage.ProtoMapValue
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -36,25 +34,20 @@ abstract class AbstractJvmProtoComparisonTest : AbstractProtoComparisonTest<Loca
return localClassFiles.associateBy { it.classId } return localClassFiles.associateBy { it.classId }
} }
override fun difference(oldData: LocalFileKotlinClass, newData: LocalFileKotlinClass): Difference? { override fun LocalFileKotlinClass.toProtoData(): ProtoData? {
val oldProto = oldData.readProto() ?: return null
val newProto = newData.readProto() ?: return null
return difference(oldProto, newProto)
}
private fun KotlinJvmBinaryClass.readProto(): ProtoMapValue? {
assert(classHeader.metadataVersion.isCompatible()) { "Incompatible class ($classHeader): $location" } assert(classHeader.metadataVersion.isCompatible()) { "Incompatible class ($classHeader): $location" }
val bytes by lazy { BitEncoding.decodeBytes(classHeader.data!!) } val bytes by lazy { BitEncoding.decodeBytes(classHeader.data!!) }
val strings by lazy { classHeader.strings!! } val strings by lazy { classHeader.strings!! }
val packageFqName = classId.packageFqName
return when (classHeader.kind) { return when (classHeader.kind) {
KotlinClassHeader.Kind.CLASS -> { KotlinClassHeader.Kind.CLASS -> {
ProtoMapValue(false, bytes, strings) ProtoMapValue(false, bytes, strings).toProtoData(packageFqName)
} }
KotlinClassHeader.Kind.FILE_FACADE, KotlinClassHeader.Kind.FILE_FACADE,
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> { KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
ProtoMapValue(true, bytes, strings) ProtoMapValue(true, bytes, strings).toProtoData(packageFqName)
} }
else -> { else -> {
null null
@@ -17,7 +17,7 @@
package org.jetbrains.kotlin.jps.incremental package org.jetbrains.kotlin.jps.incremental
import org.jetbrains.kotlin.TestWithWorkingDir 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.name.ClassId
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.utils.Printer import org.jetbrains.kotlin.utils.Printer
@@ -25,7 +25,7 @@ import java.io.File
abstract class AbstractProtoComparisonTest<PROTO_DATA> : TestWithWorkingDir() { abstract class AbstractProtoComparisonTest<PROTO_DATA> : TestWithWorkingDir() {
protected abstract fun compileAndGetClasses(sourceDir: File, outputDir: File): Map<ClassId, PROTO_DATA> 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 = protected open fun expectedOutputFile(testDir: File): File =
File(testDir, "result.out") File(testDir, "result.out")
@@ -48,32 +48,41 @@ abstract class AbstractProtoComparisonTest<PROTO_DATA> : TestWithWorkingDir() {
} }
(oldClassMap.keys.intersect(newClassMap.keys)).sortedBy { it.toString() }.forEach { classId -> (oldClassMap.keys.intersect(newClassMap.keys)).sortedBy { it.toString() }.forEach { classId ->
val oldData = oldClassMap[classId]!! val oldData = oldClassMap[classId]!!.toProtoData()
val newData = newClassMap[classId]!! val newData = newClassMap[classId]!!.toProtoData()
val diff = difference(oldData, newData)
if (diff == null) { if (oldData == null || newData == null) {
p.println("SKIPPED $classId") p.println("SKIPPED $classId")
return@forEach 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()) { if (it.isNotEmpty()) {
p.println("PROTO DIFFERENCE in $classId: ${it.sortedBy { it.name }.joinToString()}") p.println("PROTO DIFFERENCE in $classId: ${it.sortedBy { it.name }.joinToString()}")
} }
} }
val changes = arrayListOf<String>() val changesInfo = ChangesCollector().apply { collectProtoChanges(oldData, newData) }.changes()
if (diff.isClassAffected) { if (changesInfo.isEmpty()) {
changes.add("CLASS_SIGNATURE")
}
if (diff.changedMembersNames.isNotEmpty()) {
changes.add("MEMBERS\n ${diff.changedMembersNames.sorted()}")
}
if (changes.isEmpty()) {
return@forEach 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()}") p.println("CHANGES in $classId: ${changes.joinToString()}")
} }
@@ -314,8 +314,9 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
context.checkCanceled() context.checkCanceled()
val changesInfo = generatedFiles.entries.fold(CompilationResult.NO_CHANGES) { acc, (target, files) -> val changesCollector = ChangesCollector()
acc + updateIncrementalCache(files, incrementalCaches[target]!!) for ((target, files) in generatedFiles) {
updateIncrementalCache(files, incrementalCaches[target]!!, changesCollector)
} }
updateLookupStorage(chunk, lookupTracker, dataManager, dirtyFilesHolder, filesToCompile) updateLookupStorage(chunk, lookupTracker, dataManager, dirtyFilesHolder, filesToCompile)
@@ -323,7 +324,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
return OK return OK
} }
changesInfo.processChangesUsingLookups(filesToCompile.values().toSet(), dataManager, fsOperations, incrementalCaches.values) changesCollector.processChangesUsingLookups(filesToCompile.values().toSet(), dataManager, fsOperations, incrementalCaches.values)
return OK return OK
} }
@@ -432,7 +433,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
for (target in chunk.targets) { for (target in chunk.targets) {
val cache = incrementalCaches[target]!! val cache = incrementalCaches[target]!!
val removedAndDirtyFiles = filesToCompile[target] + dirtyFilesHolder.getRemovedFiles(target).map(::File) 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>, compiledFiles: Set<File>,
dataManager: BuildDataManager, dataManager: BuildDataManager,
fsOperations: FSOperationsHelper, fsOperations: FSOperationsHelper,