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 3aad308f5f2..4d4f6baf794 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt @@ -57,10 +57,6 @@ import org.jetbrains.kotlin.config.Services import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings import org.jetbrains.kotlin.jps.incremental.* -import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.DO_NOTHING -import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.RECOMPILE_ALL_IN_CHUNK_AND_DEPENDANTS -import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.RECOMPILE_OTHER_IN_CHUNK_AND_DEPENDANTS -import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.RECOMPILE_OTHER_KOTLIN_IN_CHUNK import org.jetbrains.kotlin.load.kotlin.ModuleMapping import org.jetbrains.kotlin.load.kotlin.PackageClassUtils import org.jetbrains.kotlin.load.kotlin.header.isCompatiblePackageFacadeKind @@ -198,27 +194,55 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR context.checkCanceled() - val recompilationDecision: IncrementalCacheImpl.RecompilationDecision - if (JpsUtils.isJsKotlinModule(chunk.representativeTarget())) { - recompilationDecision = DO_NOTHING - } - else { - val generatedClasses = generatedFiles.filterIsInstance() - recompilationDecision = updateKotlinIncrementalCache(compilationErrors, incrementalCaches, generatedFiles) - updateJavaMappings(chunk, compilationErrors, context, dirtyFilesHolder, filesToCompile, generatedClasses) + val isJsModule = JpsUtils.isJsKotlinModule(chunk.representativeTarget()) + val changesInfo: ChangesInfo = when { + isJsModule -> ChangesInfo.NO_CHANGES + else -> { + val generatedClasses = generatedFiles.filterIsInstance() + val info = updateKotlinIncrementalCache(compilationErrors, incrementalCaches, generatedFiles) + updateJavaMappings(chunk, compilationErrors, context, dirtyFilesHolder, filesToCompile, generatedClasses) + info + } } if (compilationErrors) { return ABORT } - if (JpsUtils.isJsKotlinModule(chunk.representativeTarget())) { + if (isJsModule) { copyJsLibraryFilesIfNeeded(chunk, project) } - if (IncrementalCompilation.ENABLED) { - val caches = filesToCompile.keySet().map { incrementalCaches[it]!! } + if (!IncrementalCompilation.ENABLED) return OK + val caches = filesToCompile.keySet().map { incrementalCaches[it]!! } + val marker = ChangesProcessor(context, chunk, allCompiledFiles, caches) + marker.processChanges(changesInfo) + return ADDITIONAL_PASS_REQUIRED + } + + class ChangesProcessor( + val context: CompileContext, + val chunk: ModuleChunk, + val allCompiledFiles: MutableSet, + val caches: List + ) { + fun processChanges(changesInfo: ChangesInfo) { + changesInfo.doProcessChanges() + } + + private fun ChangesInfo.doProcessChanges() { + when { + constantsChanged -> recompileOtherAndDependents() + protoChanged -> recompileOtherKotlinInChunk() + } + + if (inlineChanged) { + recompileInlined() + } + } + + private fun recompileInlined() { for (cache in caches) { val filesToReinline = cache.getFilesToReinline() @@ -226,34 +250,29 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR FSOperations.markDirty(context, CompilationRound.NEXT, it) } } - - when (recompilationDecision) { - RECOMPILE_ALL_IN_CHUNK_AND_DEPENDANTS -> { - allCompiledFiles.clear() - FSOperations.markDirtyRecursively(context, chunk) - } - RECOMPILE_OTHER_IN_CHUNK_AND_DEPENDANTS -> { - // Workaround for IDEA 14.0-14.0.2: extended version of markDirtyRecursively is not available - try { - Class.forName("org.jetbrains.jps.incremental.fs.CompilationRound") - - FSOperations.markDirtyRecursively(context, CompilationRound.NEXT, chunk, { file -> file !in allCompiledFiles }) - } - catch (e: ClassNotFoundException) { - allCompiledFiles.clear() - FSOperations.markDirtyRecursively(context, chunk) - } - } - RECOMPILE_OTHER_KOTLIN_IN_CHUNK -> { - FSOperations.markDirty(context, chunk, { file -> - KotlinSourceFileCollector.isKotlinSourceFile(file) && file !in allCompiledFiles - }) - } - } - return ADDITIONAL_PASS_REQUIRED } - return OK + private fun recompileEverything() { + allCompiledFiles.clear() + FSOperations.markDirtyRecursively(context, chunk) + } + + private fun recompileOtherAndDependents() { + // Workaround for IDEA 14.0-14.0.2: extended version of markDirtyRecursively is not available + try { + Class.forName("org.jetbrains.jps.incremental.fs.CompilationRound") + + FSOperations.markDirtyRecursively(context, CompilationRound.NEXT, chunk, { file -> file !in allCompiledFiles }) + } catch (e: ClassNotFoundException) { + recompileEverything() + } + } + + private fun recompileOtherKotlinInChunk() { + FSOperations.markDirty(context, chunk, { file -> + KotlinSourceFileCollector.isKotlinSourceFile(file) && file !in allCompiledFiles + }) + } } private fun doCompileModuleChunk( @@ -423,19 +442,19 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR compilationErrors: Boolean, incrementalCaches: Map, generatedFiles: List - ): IncrementalCacheImpl.RecompilationDecision { + ): ChangesInfo { incrementalCaches.values().forEach { it.saveCacheFormatVersion() } if (!IncrementalCompilation.ENABLED) { - return DO_NOTHING + return ChangesInfo.NO_CHANGES } - var recompilationDecision = DO_NOTHING + var changesInfo = ChangesInfo.NO_CHANGES for (generatedFile in generatedFiles) { val ic = incrementalCaches[generatedFile.target]!! - val newDecision = + val newChangesInfo = if (generatedFile is GeneratedJvmClass) { - ic.saveFileToCache(generatedFile.sourceFiles, generatedFile.outputClass) + ic.saveFileToCache(generatedFile) } else if (generatedFile.outputFile.isModuleMappingFile()) { ic.saveModuleMappingToCache(generatedFile.sourceFiles, generatedFile.outputFile) @@ -444,17 +463,17 @@ public class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR continue } - recompilationDecision = recompilationDecision.merge(newDecision) + changesInfo += newChangesInfo } if (!compilationErrors) { incrementalCaches.values().forEach { - val newDecision = it.clearCacheForRemovedClasses() - recompilationDecision = recompilationDecision.merge(newDecision) + val newChangesInfo = it.clearCacheForRemovedClasses() + changesInfo += newChangesInfo } } - return recompilationDecision + return changesInfo } private fun File.isModuleMappingFile() = extension == ModuleMapping.MAPPING_FILE_EXT && parentFile.name == "META-INF" @@ -686,7 +705,7 @@ private open class GeneratedFile( val outputFile: File ) -private class GeneratedJvmClass( +class GeneratedJvmClass ( target: ModuleBuildTarget, sourceFiles: Collection, outputFile: File 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 d6cfe82cb32..f955082b1c9 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/IncrementalCacheImpl.kt @@ -33,10 +33,8 @@ import org.jetbrains.jps.incremental.storage.PathStringDescriptor import org.jetbrains.jps.incremental.storage.StorageOwner import org.jetbrains.kotlin.config.IncrementalCompilation import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.jps.build.GeneratedJvmClass import org.jetbrains.kotlin.jps.build.KotlinBuilder -import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.DO_NOTHING -import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.RECOMPILE_OTHER_IN_CHUNK_AND_DEPENDANTS -import org.jetbrains.kotlin.jps.incremental.IncrementalCacheImpl.RecompilationDecision.RECOMPILE_OTHER_KOTLIN_IN_CHUNK import org.jetbrains.kotlin.jps.incremental.storage.BasicMap import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.JvmAnnotationNames @@ -213,103 +211,86 @@ public class IncrementalCacheImpl( return File(outputDir, "$internalClassName.class").canonicalPath } - private fun getRecompilationDecision(protoChanged: Boolean, constantsChanged: Boolean) = - when { - constantsChanged -> RECOMPILE_OTHER_IN_CHUNK_AND_DEPENDANTS - protoChanged -> RECOMPILE_OTHER_KOTLIN_IN_CHUNK - else -> DO_NOTHING - } - public fun saveCacheFormatVersion() { cacheFormatVersion.saveIfNeeded() } - public fun saveModuleMappingToCache(sourceFiles: Collection, file: File): RecompilationDecision { + public fun saveModuleMappingToCache(sourceFiles: Collection, file: File): ChangesInfo { val jvmClassName = JvmClassName.byInternalName(MODULE_MAPPING_FILE_NAME) - protoMap.put(jvmClassName, file.readBytes(), isPackage = false, checkChangesIsOpenPart = false) + protoMap.process(jvmClassName, file.readBytes(), isPackage = false, checkChangesIsOpenPart = false) dirtyOutputClassesMap.notDirty(MODULE_MAPPING_FILE_NAME) sourceFiles.forEach { sourceToClassesMap.add(it, jvmClassName) } - return DO_NOTHING + return ChangesInfo.NO_CHANGES } - public fun saveFileToCache(sourceFiles: Collection, kotlinClass: LocalFileKotlinClass): RecompilationDecision { - val fileBytes = kotlinClass.getFileContents() - val className = JvmClassName.byClassId(kotlinClass.getClassId()) - val header = kotlinClass.getClassHeader() + public fun saveFileToCache(generatedClass: GeneratedJvmClass): ChangesInfo { + val sourceFiles: Collection = generatedClass.sourceFiles + val kotlinClass: LocalFileKotlinClass = generatedClass.outputClass + val className = JvmClassName.byClassId(kotlinClass.classId) - dirtyOutputClassesMap.notDirty(className.getInternalName()) + dirtyOutputClassesMap.notDirty(className.internalName) sourceFiles.forEach { sourceToClassesMap.add(it, className) classToSourcesMap.add(className, it) } - inlineFunctionsMap.process(className, fileBytes) - - val decision = when { + val header = kotlinClass.classHeader + val changesInfo = when { header.isCompatiblePackageFacadeKind() -> - getRecompilationDecision( - protoChanged = protoMap.put(className, BitEncoding.decodeBytes(header.annotationData!!), isPackage = true), - constantsChanged = false - ) + protoMap.process(kotlinClass, isPackage = true) header.isCompatibleFileFacadeKind() -> { assert(sourceFiles.size() == 1) { "Package part from several source files: $sourceFiles" } packagePartMap.addPackagePart(className) - getRecompilationDecision( - protoChanged = protoMap.put(className, BitEncoding.decodeBytes(header.annotationData!!), isPackage = true), - constantsChanged = constantsMap.process(className, fileBytes) - ) - } - header.isCompatibleClassKind() -> - when (header.classKind!!) { - JvmAnnotationNames.KotlinClass.Kind.CLASS -> getRecompilationDecision( - protoChanged = protoMap.put(className, BitEncoding.decodeBytes(header.annotationData!!), isPackage = false), - constantsChanged = constantsMap.process(className, fileBytes) - ) - JvmAnnotationNames.KotlinClass.Kind.LOCAL_CLASS, JvmAnnotationNames.KotlinClass.Kind.ANONYMOUS_OBJECT -> DO_NOTHING - } + protoMap.process(kotlinClass, isPackage = true) + + constantsMap.process(kotlinClass) + } + header.isCompatibleClassKind() && JvmAnnotationNames.KotlinClass.Kind.CLASS == header.classKind -> + protoMap.process(kotlinClass, isPackage = false) + + constantsMap.process(kotlinClass) + + inlineFunctionsMap.process(kotlinClass) header.syntheticClassKind == JvmAnnotationNames.KotlinSyntheticClass.Kind.PACKAGE_PART -> { assert(sourceFiles.size() == 1) { "Package part from several source files: $sourceFiles" } - packagePartMap.addPackagePart(className) - getRecompilationDecision( - protoChanged = false, - constantsChanged = constantsMap.process(className, fileBytes) - ) - } - else -> { - DO_NOTHING + constantsMap.process(kotlinClass) + + inlineFunctionsMap.process(kotlinClass) } + else -> ChangesInfo.NO_CHANGES } - if (decision != DO_NOTHING) { - KotlinBuilder.LOG.debug("$decision because $className is changed") - } - return decision + + changesInfo.logIfSomethingChanged(className) + return changesInfo } - public fun clearCacheForRemovedClasses(): RecompilationDecision { - var recompilationDecision = DO_NOTHING - for (internalClassName in dirtyOutputClassesMap.getDirtyOutputClasses()) { - val className = JvmClassName.byInternalName(internalClassName) + private fun ChangesInfo.logIfSomethingChanged(className: JvmClassName) { + if (this == ChangesInfo.NO_CHANGES) return - val newDecision = getRecompilationDecision( - protoChanged = internalClassName in protoMap, - constantsChanged = internalClassName in constantsMap - ) - if (newDecision != DO_NOTHING) { - KotlinBuilder.LOG.debug("$newDecision because $internalClassName is removed") - } + KotlinBuilder.LOG.debug("$className is changed: $this") + } - recompilationDecision = recompilationDecision.merge(newDecision) + public fun clearCacheForRemovedClasses(): ChangesInfo { + val dirtyClasses = dirtyOutputClassesMap + .getDirtyOutputClasses() + .map(JvmClassName::byInternalName) + .toList() - protoMap.remove(className) - packagePartMap.remove(className) - constantsMap.remove(className) - inlineFunctionsMap.remove(className) + val changesInfo = dirtyClasses.fold(ChangesInfo.NO_CHANGES) { info, className -> + val internalName = className.internalName + val newInfo = ChangesInfo(protoChanged = internalName in protoMap, + constantsChanged = internalName in constantsMap) + newInfo.logIfSomethingChanged(className) + info + newInfo + } + + dirtyClasses.forEach { + protoMap.remove(it) + packagePartMap.remove(it) + constantsMap.remove(it) + inlineFunctionsMap.remove(it) } dirtyOutputClassesMap.clean() - return recompilationDecision + return changesInfo } override fun getObsoletePackageParts(): Collection { @@ -342,19 +323,27 @@ public class IncrementalCacheImpl( private inner class ProtoMap(storageFile: File) : BasicMap(storageFile, ByteArrayExternalizer) { - public fun put(className: JvmClassName, data: ByteArray, isPackage: Boolean, checkChangesIsOpenPart: Boolean = true): Boolean { - val key = className.getInternalName() + public fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean, checkChangesIsOpenPart: Boolean = true): ChangesInfo { + val header = kotlinClass.classHeader + val bytes = BitEncoding.decodeBytes(header.annotationData!!) + return put(kotlinClass.className, bytes, isPackage, checkChangesIsOpenPart) + } + + public fun process(className: JvmClassName, data: ByteArray, isPackage: Boolean, checkChangesIsOpenPart: Boolean): ChangesInfo { + return put(className, data, isPackage, checkChangesIsOpenPart) + } + + private fun put(className: JvmClassName, data: ByteArray, isPackage: Boolean, checkChangesIsOpenPart: Boolean): ChangesInfo { + val key = className.internalName val oldData = storage[key] - if (Arrays.equals(data, oldData)) { - return false - } - storage.put(key, data) - if (oldData != null && checkChangesIsOpenPart && isOpenPartNotChanged(oldData, data, isPackage)) { - return false + if (!Arrays.equals(data, oldData)) { + storage.put(key, data) } - return true + return ChangesInfo(protoChanged = oldData == null || + !checkChangesIsOpenPart || + !isOpenPartNotChanged(oldData, data, isPackage)) } public fun get(className: JvmClassName): ByteArray? { @@ -434,24 +423,24 @@ public class IncrementalCacheImpl( return if (result.isEmpty()) null else result } - public fun process(className: JvmClassName, bytes: ByteArray): Boolean { - return put(className, getConstantsMap(bytes)) + public fun process(kotlinClass: LocalFileKotlinClass): ChangesInfo { + return put(kotlinClass.className, getConstantsMap(kotlinClass.fileContents)) } - private fun put(className: JvmClassName, constantsMap: Map?): Boolean { + private fun put(className: JvmClassName, constantsMap: Map?): ChangesInfo { val key = className.getInternalName() val oldMap = storage[key] - if (oldMap == constantsMap) { - return false - } + if (oldMap == constantsMap) return ChangesInfo.NO_CHANGES + if (constantsMap != null) { storage.put(key, constantsMap) } else { storage.remove(key) } - return true + + return ChangesInfo(constantsChanged = true) } public fun remove(className: JvmClassName) { @@ -553,11 +542,11 @@ public class IncrementalCacheImpl( return result } - public fun process(className: JvmClassName, bytes: ByteArray): Boolean { - return put(className, getInlineFunctionsMap(bytes)) + public fun process(kotlinClass: LocalFileKotlinClass): ChangesInfo { + return put(kotlinClass.className, getInlineFunctionsMap(kotlinClass.fileContents)) } - private fun put(className: JvmClassName, newMap: Map): Boolean { + private fun put(className: JvmClassName, newMap: Map): ChangesInfo { val internalName = className.internalName val oldMap = storage[internalName] ?: emptyMap() @@ -580,10 +569,9 @@ public class IncrementalCacheImpl( if (changed.isNotEmpty()) { dirtyInlineFunctionsMap.put(className, changed.toList()) - return true } - return false + return ChangesInfo(inlineChanged = changed.isNotEmpty()) } public fun remove(className: JvmClassName) { @@ -736,19 +724,24 @@ public class IncrementalCacheImpl( return mutableMapping } } - - enum class RecompilationDecision { - DO_NOTHING, - RECOMPILE_OTHER_KOTLIN_IN_CHUNK, - RECOMPILE_OTHER_IN_CHUNK_AND_DEPENDANTS, - RECOMPILE_ALL_IN_CHUNK_AND_DEPENDANTS; - - fun merge(other: RecompilationDecision): RecompilationDecision { - return if (other.ordinal() > this.ordinal()) other else this - } - } } +data class ChangesInfo( + public val protoChanged: Boolean = false, + public val constantsChanged: Boolean = false, + public val inlineChanged: Boolean = false +) { + companion object { + public val NO_CHANGES: ChangesInfo = ChangesInfo() + } + + public fun plus(other: ChangesInfo): ChangesInfo = + ChangesInfo(protoChanged || other.protoChanged, + constantsChanged || other.constantsChanged, + inlineChanged || other.inlineChanged) +} + + public fun BuildDataPaths.getKotlinCacheVersion(target: BuildTarget<*>): CacheFormatVersion = CacheFormatVersion(getTargetDataRoot(target)) private data class KotlinIncrementalStorageProvider( diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/LocalFileKotlinClass.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/LocalFileKotlinClass.kt index 038d44dce2b..86e24b1c7e5 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/LocalFileKotlinClass.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/incremental/LocalFileKotlinClass.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.jps.incremental import org.jetbrains.kotlin.load.kotlin.FileBasedKotlinClass import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.resolve.jvm.JvmClassName import java.io.File class LocalFileKotlinClass private constructor( @@ -39,6 +40,8 @@ class LocalFileKotlinClass private constructor( } } + public val className: JvmClassName by lazy { JvmClassName.byClassId(classId) } + override fun getLocation() = file.getAbsolutePath() public override fun getFileContents(): ByteArray = fileContents