diff --git a/build-common/src/org/jetbrains/kotlin/incremental/AbstractIncrementalCache.kt b/build-common/src/org/jetbrains/kotlin/incremental/AbstractIncrementalCache.kt index fedb2949dc8..337c09af52b 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/AbstractIncrementalCache.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/AbstractIncrementalCache.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.incremental import com.intellij.util.io.EnumeratorStringDescriptor import org.jetbrains.kotlin.incremental.storage.* import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.metadata.deserialization.Flags import org.jetbrains.kotlin.metadata.deserialization.NameResolver import org.jetbrains.kotlin.metadata.deserialization.TypeTable import org.jetbrains.kotlin.metadata.deserialization.supertypes @@ -34,12 +35,15 @@ interface IncrementalCacheCommon { val thisWithDependentCaches: Iterable> fun classesFqNamesBySources(files: Iterable): Collection fun getSubtypesOf(className: FqName): Sequence + fun getSupertypesOf(className: FqName): Sequence fun getSourceFileIfClass(fqName: FqName): File? fun markDirty(removedAndCompiledSources: Collection) fun clearCacheForRemovedClasses(changesCollector: ChangesCollector) fun getComplementaryFilesRecursive(dirtyFiles: Collection): Collection fun updateComplementaryFiles(dirtyFiles: Collection, expectActualTracker: ExpectActualTrackerImpl) fun dump(): String + + fun isSealed(className: FqName): Boolean? } /** @@ -50,6 +54,7 @@ abstract class AbstractIncrementalCache( protected val pathConverter: FileToPathConverter ) : BasicMapsOwner(workingDir), IncrementalCacheCommon { companion object { + private val CLASS_ATTRIBUTES = "class-attributes" private val SUBTYPES = "subtypes" private val SUPERTYPES = "supertypes" private val CLASS_FQ_NAME_TO_SOURCE = "class-fq-name-to-source" @@ -71,6 +76,7 @@ abstract class AbstractIncrementalCache( result } + internal val classAttributesMap = registerMap(ClassAttributesMap(CLASS_ATTRIBUTES.storageFile)) private val subtypesMap = registerMap(SubtypesMap(SUBTYPES.storageFile)) private val supertypesMap = registerMap(SupertypesMap(SUPERTYPES.storageFile)) protected val classFqNameToSourceMap = registerMap(ClassFqNameToSourceMap(CLASS_FQ_NAME_TO_SOURCE.storageFile, pathConverter)) @@ -90,6 +96,14 @@ abstract class AbstractIncrementalCache( override fun getSubtypesOf(className: FqName): Sequence = subtypesMap[className].asSequence() + override fun getSupertypesOf(className: FqName): Sequence { + return supertypesMap[className].asSequence() + } + + override fun isSealed(className: FqName): Boolean? { + return classAttributesMap[className]?.isSealed + } + override fun getSourceFileIfClass(fqName: FqName): File? = classFqNameToSourceMap[fqName] @@ -118,6 +132,7 @@ abstract class AbstractIncrementalCache( supertypesMap[child] = parents classFqNameToSourceMap[child] = srcFile + classAttributesMap[child] = ICClassesAttributes(ProtoBuf.Modality.SEALED == Flags.MODALITY.get(proto.flags)) } protected fun removeAllFromClassStorage(removedClasses: Collection, changesCollector: ChangesCollector) { @@ -152,7 +167,10 @@ abstract class AbstractIncrementalCache( } } - removedFqNames.forEach { classFqNameToSourceMap.remove(it) } + removedFqNames.forEach { + classFqNameToSourceMap.remove(it) + classAttributesMap.remove(it) + } } protected class ClassFqNameToSourceMap( diff --git a/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt b/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt index bf66f3a8e74..90499179685 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt @@ -19,12 +19,14 @@ package org.jetbrains.kotlin.incremental import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.deserialization.Flags import org.jetbrains.kotlin.metadata.deserialization.NameResolver +import org.jetbrains.kotlin.metadata.deserialization.supertypes import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.protobuf.MessageLite import org.jetbrains.kotlin.serialization.deserialization.getClassId class ChangesCollector { private val removedMembers = hashMapOf>() + private val changedParents = hashMapOf>() private val changedMembers = hashMapOf>() private val areSubclassesAffected = hashMapOf() @@ -47,6 +49,10 @@ class ChangesCollector { changes.add(ChangeInfo.SignatureChanged(fqName, areSubclassesAffected)) } + for ((fqName, changedParents) in changedParents) { + changes.add(ChangeInfo.ParentsChanged(fqName, changedParents)) + } + return changes } @@ -79,12 +85,12 @@ class ChangesCollector { } if (oldData == null) { - newData!!.collectAll(isRemoved = false, collectAllMembersForNewClass = collectAllMembersForNewClass) + newData!!.collectAll(isRemoved = false, isAdded = true, collectAllMembersForNewClass = collectAllMembersForNewClass) return } if (newData == null) { - oldData.collectAll(isRemoved = true) + oldData.collectAll(isRemoved = true, isAdded = false) return } @@ -98,6 +104,7 @@ class ChangesCollector { collectSignature(oldData, diff.areSubclassesAffected) } collectChangedMembers(fqName, diff.changedMembersNames) + addChangedParents(fqName, diff.changedSupertypes) } is PackagePartProtoData -> { collectSignature(oldData, areSubclassesAffected = true) @@ -121,10 +128,11 @@ class ChangesCollector { private fun T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List): Set = members.flatMap { this.it().filterNot { it.isPrivate }.names(nameResolver) }.toSet() - private fun ProtoData.collectAll(isRemoved: Boolean, collectAllMembersForNewClass: Boolean = false) = + //TODO remember all sealed parent classes + private fun ProtoData.collectAll(isRemoved: Boolean, isAdded: Boolean, collectAllMembersForNewClass: Boolean = false) = when (this) { is PackagePartProtoData -> collectAllFromPackage(isRemoved) - is ClassProtoData -> collectAllFromClass(isRemoved, collectAllMembersForNewClass) + is ClassProtoData -> collectAllFromClass(isRemoved, isAdded, collectAllMembersForNewClass) } private fun PackagePartProtoData.collectAllFromPackage(isRemoved: Boolean) { @@ -143,7 +151,7 @@ class ChangesCollector { } } - private fun ClassProtoData.collectAllFromClass(isRemoved: Boolean, collectAllMembersForNewClass: Boolean = false) { + private fun ClassProtoData.collectAllFromClass(isRemoved: Boolean, isAdded: Boolean, collectAllMembersForNewClass: Boolean = false) { val classFqName = nameResolver.getClassId(proto.fqName).asSingleFqName() val kind = Flags.CLASS_KIND.get(proto.flags) @@ -162,6 +170,23 @@ class ChangesCollector { collectSignature(classFqName, areSubclassesAffected = true) } + + if (isRemoved || isAdded) { + collectChangedParents(classFqName, proto.supertypeList) + } + } + + private fun addChangedParents(fqName: FqName, parents: Collection) { + if (parents.isNotEmpty()) { + changedParents.getOrPut(fqName) { HashSet() }.addAll(parents) + } + } + + private fun ClassProtoData.collectChangedParents(fqName: FqName, parents: Collection) { + val changedParentsFqNames = parents.map { type -> + nameResolver.getClassId(type.className).asSingleFqName() + } + addChangedParents(fqName, changedParentsFqNames) } private fun ClassProtoData.getNonPrivateMemberNames(): Set { diff --git a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt index 29e89f95de9..c5f01e1824b 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt @@ -145,6 +145,7 @@ open class IncrementalJvmCache( } protoMap.remove(className, changesCollector) classFqNameToSourceMap.remove(className.fqNameForClassNameWithoutDollars) + classAttributesMap.remove(className.fqNameForClassNameWithoutDollars) internalNameToSource.remove(className.internalName) // TODO NO_CHANGES? (delegates only) @@ -568,6 +569,7 @@ sealed class ChangeInfo(val fqName: FqName) { class SignatureChanged(fqName: FqName, val areSubclassesAffected: Boolean) : ChangeInfo(fqName) + class ParentsChanged(fqName: FqName, val parentsChanged: Collection) : ChangeInfo(fqName) protected open fun toStringProperties(): String = "fqName = $fqName" diff --git a/build-common/src/org/jetbrains/kotlin/incremental/buildUtil.kt b/build-common/src/org/jetbrains/kotlin/incremental/buildUtil.kt index 745453842f6..cf215b95ab5 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/buildUtil.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/buildUtil.kt @@ -136,7 +136,8 @@ fun LookupStorage.update( data class DirtyData( val dirtyLookupSymbols: Collection = emptyList(), - val dirtyClassesFqNames: Collection = emptyList() + val dirtyClassesFqNames: Collection = emptyList(), + val dirtyClassesFqNamesForceRecompile: Collection = emptyList() ) fun ChangesCollector.getDirtyData( @@ -146,6 +147,9 @@ fun ChangesCollector.getDirtyData( val dirtyLookupSymbols = HashSet() val dirtyClassesFqNames = HashSet() + val sealedParents = HashMap>() + val notSealedParents = HashSet() + for (change in changes()) { reporter.reportVerbose { "Process $change" } @@ -170,10 +174,35 @@ fun ChangesCollector.getDirtyData( } fqNames.mapTo(dirtyLookupSymbols) { LookupSymbol(SAM_LOOKUP_NAME.asString(), it.asString()) } + } else if (change is ChangeInfo.ParentsChanged) { + fun FqName.isSealed(): Boolean { + if (notSealedParents.contains(this)) return false + if (sealedParents.containsKey(this)) return true + return isSealed(this, caches).also { sealed -> + if (sealed) { + sealedParents[this] = HashSet() + } else { + notSealedParents.add(this) + } + } + } + change.parentsChanged.forEach { parent -> + if (parent.isSealed()) { + sealedParents.getOrPut(parent) { HashSet() }.add(change.fqName) + } + } } } - return DirtyData(dirtyLookupSymbols, dirtyClassesFqNames) + val forceRecompile = HashSet().apply { + addAll(sealedParents.keys) + //we should recompile all inheritors with parent sealed class: add known subtypes + addAll(sealedParents.keys.flatMap { withSubtypes(it, caches) }) + //we should recompile all inheritors with parent sealed class: add new subtypes + addAll(sealedParents.values.flatten()) + } + + return DirtyData(dirtyLookupSymbols, dirtyClassesFqNames, forceRecompile) } fun mapLookupSymbolsToFiles( @@ -217,6 +246,11 @@ fun mapClassesFqNamesToFiles( return fqNameToAffectedFiles.values.flattenTo(HashSet()) } +fun isSealed( + fqName: FqName, + caches: Iterable +): Boolean = caches.any { it.isSealed(fqName) ?: false } + fun withSubtypes( typeFqName: FqName, caches: Iterable diff --git a/build-common/src/org/jetbrains/kotlin/incremental/protoDifferenceUtils.kt b/build-common/src/org/jetbrains/kotlin/incremental/protoDifferenceUtils.kt index 9b84e4857fb..8af998ce9c7 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/protoDifferenceUtils.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/protoDifferenceUtils.kt @@ -28,12 +28,14 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.protobuf.MessageLite import org.jetbrains.kotlin.serialization.deserialization.ProtoEnumFlags import org.jetbrains.kotlin.serialization.deserialization.descriptorVisibility +import org.jetbrains.kotlin.serialization.deserialization.getClassId import java.util.* data class Difference( val isClassAffected: Boolean = false, val areSubclassesAffected: Boolean = false, - val changedMembersNames: Set = emptySet() + val changedMembersNames: Set = emptySet(), + val changedSupertypes: Set = emptySet() ) sealed class ProtoData @@ -187,6 +189,7 @@ class DifferenceCalculatorForClass( var isClassAffected = false var areSubclassesAffected = false + val changedSupertypes = HashSet() val names = hashSetOf() val classIsSealed = newProto.isSealed && oldProto.isSealed @@ -247,12 +250,21 @@ class DifferenceCalculatorForClass( ProtoBufClassKind.FLAGS, ProtoBufClassKind.FQ_NAME, ProtoBufClassKind.TYPE_PARAMETER_LIST, - ProtoBufClassKind.SUPERTYPE_LIST, - ProtoBufClassKind.SUPERTYPE_ID_LIST, ProtoBufClassKind.JS_EXT_CLASS_ANNOTATION_LIST -> { isClassAffected = true areSubclassesAffected = true } + + ProtoBufClassKind.SUPERTYPE_LIST, + ProtoBufClassKind.SUPERTYPE_ID_LIST -> { + isClassAffected = true + areSubclassesAffected = true + + val oldSupertypes = oldProto.supertypeList.map { oldNameResolver.getClassId(it.className).asSingleFqName() } + val newSupertypes = newProto.supertypeList.map { newNameResolver.getClassId(it.className).asSingleFqName() } + val changed = (oldSupertypes union newSupertypes) subtract (oldSupertypes intersect newSupertypes) + changedSupertypes.addAll(changed) + } ProtoBufClassKind.JVM_EXT_CLASS_MODULE_NAME, ProtoBufClassKind.JS_EXT_CLASS_CONTAINING_FILE_ID -> { // TODO @@ -281,7 +293,7 @@ class DifferenceCalculatorForClass( } } - return Difference(isClassAffected, areSubclassesAffected, names) + return Difference(isClassAffected, areSubclassesAffected, names, changedSupertypes) } } diff --git a/build-common/src/org/jetbrains/kotlin/incremental/storage/ClassAttributesMap.kt b/build-common/src/org/jetbrains/kotlin/incremental/storage/ClassAttributesMap.kt new file mode 100644 index 00000000000..7b853709576 --- /dev/null +++ b/build-common/src/org/jetbrains/kotlin/incremental/storage/ClassAttributesMap.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2015 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.storage + +import com.intellij.util.io.DataExternalizer +import org.jetbrains.kotlin.name.FqName +import java.io.DataInput +import java.io.DataOutput +import java.io.File + +internal data class ICClassesAttributes(val isSealed: Boolean) + +internal object ICClassesAttributesExternalizer : DataExternalizer { + override fun read(input: DataInput): ICClassesAttributes { + return ICClassesAttributes(input.readBoolean()) + } + + override fun save(output: DataOutput, value: ICClassesAttributes) { + output.writeBoolean(value.isSealed) + } +} + +internal open class ClassAttributesMap( + storageFile: File +) : BasicStringMap(storageFile, ICClassesAttributesExternalizer) { + override fun dumpValue(value: ICClassesAttributes): String = value.toString() + + operator fun set(key: FqName, value: ICClassesAttributes) { + storage[key.asString()] = value + } + + operator fun get(key: FqName): ICClassesAttributes? = storage[key.asString()] + + fun remove(key: FqName) { + storage.remove(key.asString()) + } +} diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt index ecdbf02d6b2..450e25eb3d3 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt @@ -310,9 +310,10 @@ abstract class IncrementalCompilerRunner< } if (compilationMode is CompilationMode.Rebuild) break - val (dirtyLookupSymbols, dirtyClassFqNames) = changesCollector.getDirtyData(listOf(caches.platformCache), reporter) + val (dirtyLookupSymbols, dirtyClassFqNames, forceRecompile) = changesCollector.getDirtyData(listOf(caches.platformCache), reporter) val compiledInThisIterationSet = sourcesToCompile.toHashSet() + val forceToRecompileFiles = mapClassesFqNamesToFiles(listOf(caches.platformCache), forceRecompile, reporter) with(dirtySources) { clear() addAll(mapLookupSymbolsToFiles(caches.lookupCache, dirtyLookupSymbols, reporter, excludes = compiledInThisIterationSet)) @@ -324,6 +325,9 @@ abstract class IncrementalCompilerRunner< excludes = compiledInThisIterationSet ) ) + if (!compiledInThisIterationSet.containsAll(forceToRecompileFiles)) { + addAll(forceToRecompileFiles) + } } buildDirtyLookupSymbols.addAll(dirtyLookupSymbols) diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJsCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJsCompilerRunner.kt index 09c17ebfd2f..963fa236ddd 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJsCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJsCompilerRunner.kt @@ -125,7 +125,7 @@ class IncrementalJsCompilerRunner( val removedClassesChanges = getRemovedClassesChanges(caches, changedFiles) dirtyFiles.addByDirtySymbols(removedClassesChanges.dirtyLookupSymbols) dirtyFiles.addByDirtyClasses(removedClassesChanges.dirtyClassesFqNames) - + dirtyFiles.addByDirtyClasses(removedClassesChanges.dirtyClassesFqNamesForceRecompile) return CompilationMode.Incremental(dirtyFiles) } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt index c85f8e6d573..9c5da3b4972 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt @@ -227,7 +227,7 @@ class IncrementalJvmCompilerRunner( dirtyFiles.addByDirtySymbols(androidLayoutChanges) dirtyFiles.addByDirtySymbols(removedClassesChanges.dirtyLookupSymbols) dirtyFiles.addByDirtyClasses(removedClassesChanges.dirtyClassesFqNames) - + dirtyFiles.addByDirtyClasses(removedClassesChanges.dirtyClassesFqNamesForceRecompile) return CompilationMode.Incremental(dirtyFiles) } diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractProtoComparisonTest.kt b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractProtoComparisonTest.kt index 4065354f67c..da16372d4c1 100644 --- a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractProtoComparisonTest.kt +++ b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractProtoComparisonTest.kt @@ -90,6 +90,8 @@ abstract class AbstractProtoComparisonTest : TestWithWorkingDir() { when (it) { is ChangeInfo.SignatureChanged -> "CLASS_SIGNATURE" is ChangeInfo.MembersChanged -> "MEMBERS\n ${it.names.sorted()}" + is ChangeInfo.ParentsChanged -> "PARENTS\n ${it.parentsChanged.map { it.asString()}.sorted()}" + } }.sorted() diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt index d0b97301a60..04afe480d2c 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt @@ -250,7 +250,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { removedClasses.forEach { changesCollector.collectSignature(FqName(it), areSubclassesAffected = true) } val affectedByRemovedClasses = changesCollector.getDirtyFiles(incrementalCaches.values, kotlinContext.lookupStorageManager) - fsOperations.markFilesForCurrentRound(affectedByRemovedClasses) + fsOperations.markFilesForCurrentRound(affectedByRemovedClasses.dirtyFiles + affectedByRemovedClasses.forceRecompileTogether) } override fun chunkBuildFinished(context: CompileContext, chunk: ModuleChunk) { @@ -695,21 +695,36 @@ private fun ChangesCollector.processChangesUsingLookups( reporter.reportVerbose { "Start processing changes" } val dirtyFiles = getDirtyFiles(allCaches, lookupStorageManager) - fsOperations.markInChunkOrDependents(dirtyFiles.asIterable(), excludeFiles = compiledFiles) + // if list of inheritors of sealed class has changed it should be recompiled with all the inheritors + // Here we have a small optimization. Do not recompile the bunch if ALL these files were recompiled during the previous round. + val excludeFiles = if (compiledFiles.containsAll(dirtyFiles.forceRecompileTogether)) + compiledFiles + else + compiledFiles.minus(dirtyFiles.forceRecompileTogether) + fsOperations.markInChunkOrDependents( + (dirtyFiles.dirtyFiles + dirtyFiles.forceRecompileTogether).asIterable(), + excludeFiles = excludeFiles + ) reporter.reportVerbose { "End of processing changes" } } +data class FilesToRecompile(val dirtyFiles: Set, val forceRecompileTogether: Set) + private fun ChangesCollector.getDirtyFiles( caches: Iterable, lookupStorageManager: JpsLookupStorageManager -): Set { +): FilesToRecompile { val reporter = JpsICReporter() - val (dirtyLookupSymbols, dirtyClassFqNames) = getDirtyData(caches, reporter) + val (dirtyLookupSymbols, dirtyClassFqNames, forceRecompile) = getDirtyData(caches, reporter) val dirtyFilesFromLookups = lookupStorageManager.withLookupStorage { mapLookupSymbolsToFiles(it, dirtyLookupSymbols, reporter) } - return dirtyFilesFromLookups + mapClassesFqNamesToFiles(caches, dirtyClassFqNames, reporter) + return FilesToRecompile( + dirtyFilesFromLookups + mapClassesFqNamesToFiles(caches, dirtyClassFqNames, reporter), + mapClassesFqNamesToFiles(caches, forceRecompile, reporter) + ) + } private fun getLookupTracker(project: JpsProject, representativeTarget: KotlinModuleBuildTarget<*>): LookupTracker {