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 61465eff7de..52f844dfffe 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt @@ -24,8 +24,6 @@ import gnu.trove.THashSet import org.jetbrains.jps.ModuleChunk import org.jetbrains.jps.builders.BuildTarget import org.jetbrains.jps.builders.DirtyFilesHolder -import org.jetbrains.jps.builders.impl.BuildTargetRegistryImpl -import org.jetbrains.jps.builders.impl.TargetOutputIndexImpl import org.jetbrains.jps.builders.java.JavaBuilderUtil import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor import org.jetbrains.jps.incremental.* @@ -59,6 +57,7 @@ import org.jetbrains.kotlin.load.kotlin.ModuleMapping import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents import org.jetbrains.kotlin.modules.TargetId +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.progress.CompilationCanceledException import org.jetbrains.kotlin.progress.CompilationCanceledStatus import org.jetbrains.kotlin.utils.LibraryUtils @@ -687,12 +686,12 @@ private fun processChanges( compiledFiles: Set, allCompiledFiles: MutableSet, dataManager: BuildDataManager, - caches: List, + caches: Collection, compilationResult: CompilationResult, fsOperations: FSOperationsHelper ) { if (IncrementalCompilation.isExperimental()) { - compilationResult.doProcessChangesUsingLookups(compiledFiles, dataManager, fsOperations) + doProcessChangesUsingLookups(compilationResult.changes.asIterable(), compiledFiles, dataManager, fsOperations, caches) } else { compilationResult.doProcessChanges(compiledFiles, allCompiledFiles, caches, fsOperations) @@ -728,33 +727,72 @@ private fun CompilationResult.doProcessChanges( } } -private fun CompilationResult.doProcessChangesUsingLookups( +private fun doProcessChangesUsingLookups( + changes: Iterable, compiledFiles: Set, dataManager: BuildDataManager, - fsOperations: FSOperationsHelper + fsOperations: FSOperationsHelper, + caches: Collection ) { + val dirtyLookupSymbols = HashSet() val lookupStorage = dataManager.getStorage(KotlinDataContainerTarget, LookupStorageProvider) KotlinBuilder.LOG.debug("Start processing changes") - // TODO group by fqName? - for (change in changes) { - KotlinBuilder.LOG.debug("Process $change") - - if (change !is ChangeInfo.MembersChanged) continue - - val files = change.names.asSequence() - .flatMap { lookupStorage.get(LookupSymbol(it, change.fqName.asString())).asSequence() } - .map(::File) - - fsOperations.markFiles(files.asIterable(), excludeFiles = compiledFiles) + val changedSignatureFqNames = changes.filterIsInstance().map { it.fqName } + for (classFqName in getSubtypesOf(changedSignatureFqNames, caches)) { + val scope = classFqName.parent().asString() + val name = classFqName.shortName().identifier + dirtyLookupSymbols.add(LookupSymbol(name, scope)) } + for (change in changes.filterIsInstance()) { + val scopes = getSubtypesOf(listOf(change.fqName), caches).map { it.asString() } + + for (name in change.names) { + for (scope in scopes) { + dirtyLookupSymbols.add(LookupSymbol(name, scope)) + } + } + } + + val dirtyFiles = HashSet() + + for (lookup in dirtyLookupSymbols) { + val affectedFiles = lookupStorage.get(lookup).map(::File) + + KotlinBuilder.LOG.debug { "${lookup.scope}#${lookup.name} caused recompilation of: $affectedFiles" } + + dirtyFiles.addAll(affectedFiles) + } + + fsOperations.markFiles(dirtyFiles.asIterable(), excludeFiles = compiledFiles) KotlinBuilder.LOG.debug("End of processing changes") } -private val Iterable>.moduleTargets: Iterable - get() = filterIsInstance(ModuleBuildTarget::class.java) +/** + * Gets subtypes of given types inclusively + */ +private fun getSubtypesOf( + typeFqNames: Iterable, + caches: Collection +): Set { + val types = typeFqNames.toCollection(LinkedList()) + val subtypes = hashSetOf() + + while (types.isNotEmpty()) { + val unprocessedType = types.pollFirst() + + caches.asSequence() + .flatMap { it.getSubtypesOf(unprocessedType) } + .filter { it !in subtypes } + .forEach { types.addLast(it) } + + subtypes.add(unprocessedType) + } + + return subtypes +} private fun getLookupTracker(project: JpsProject): LookupTracker { var lookupTracker = LookupTracker.DO_NOTHING @@ -855,3 +893,9 @@ class GeneratedJvmClass ( "Couldn't load KotlinClass from $outputFile; it may happen because class doesn't have valid Kotlin annotations" } } + +private inline fun Logger.debug(message: ()->String) { + if (isDebugEnabled) { + debug(message()) + } +} diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt index 37ae9427f03..df11900c657 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt @@ -105,8 +105,8 @@ class IncrementalCacheImpl( private val dependents = arrayListOf() private val outputDir = requireNotNull(target.outputDir) { "Target is expected to have output directory: $target" } - private val dependentsWithThis: Iterable - get() = dependents + this + private val dependentsWithThis: Sequence + get() = sequenceOf(this).plus(dependents.asSequence()) override fun registerInline(fromPath: String, jvmSignature: String, toPath: String) { inlinedTo.add(fromPath, jvmSignature, toPath) @@ -142,6 +142,9 @@ class IncrementalCacheImpl( return result.map { File(it) } } + fun getSubtypesOf(className: FqName): Sequence = + dependentsWithThis.flatMap { it.subtypesMap[className].asSequence() } + fun cleanDirtyInlineFunctions() { dirtyInlineFunctionsMap.clean() } @@ -600,9 +603,14 @@ class IncrementalCacheImpl( val supertypes = classData.classProto.supertypes(TypeTable(classData.classProto.typeTable)) val parents = supertypes.map { classData.nameResolver.getClassId(it.className).asSingleFqName() } .filter { it.asString() != "kotlin.Any" } + .toSet() val child = kotlinClass.classId.asSingleFqName() parents.forEach { subtypesMap.add(it, child) } + + val removedSupertypes = supertypesMap[child].filter { it !in parents } + removedSupertypes.forEach { subtypesMap.removeValues(it, setOf(child)) } + supertypesMap[child] = parents }