Creating shared module "build", copying lookup storage, tracker and incremental cache related files into it from jps plugin
Original commit: 5ead2edaa0
This commit is contained in:
committed by
Zalim Bashorov
parent
a2d58cca63
commit
693be92b92
@@ -10,6 +10,7 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="idea-jps-common" />
|
||||
<orderEntry type="module" module-name="cli-common" />
|
||||
<orderEntry type="module" module-name="build" />
|
||||
<orderEntry type="library" name="jps" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="jps-test" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="idea-full" level="project" />
|
||||
|
||||
@@ -39,6 +39,9 @@ import org.jetbrains.jps.model.ex.JpsElementChildRoleBase
|
||||
import org.jetbrains.jps.model.java.JpsJavaClasspathKind
|
||||
import org.jetbrains.jps.model.java.JpsJavaExtensionService
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import org.jetbrains.kotlin.build.GeneratedFile
|
||||
import org.jetbrains.kotlin.build.GeneratedJvmClass
|
||||
import org.jetbrains.kotlin.build.isModuleMappingFile
|
||||
import org.jetbrains.kotlin.cli.common.KotlinVersion
|
||||
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
@@ -54,10 +57,10 @@ import org.jetbrains.kotlin.config.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.config.Services
|
||||
import org.jetbrains.kotlin.daemon.common.isDaemonEnabled
|
||||
import org.jetbrains.kotlin.incremental.*
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings
|
||||
import org.jetbrains.kotlin.jps.incremental.*
|
||||
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
|
||||
@@ -67,7 +70,6 @@ import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||
import org.jetbrains.kotlin.utils.JsLibraryUtils
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
@@ -249,7 +251,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
return OK
|
||||
}
|
||||
|
||||
val generatedClasses = generatedFiles.filterIsInstance<GeneratedJvmClass>()
|
||||
val generatedClasses = generatedFiles.filterIsInstance<GeneratedJvmClass<ModuleBuildTarget>>()
|
||||
updateJavaMappings(chunk, compilationErrors, context, dirtyFilesHolder, filesToCompile, generatedClasses)
|
||||
|
||||
if (!IncrementalCompilation.isEnabled()) {
|
||||
@@ -301,7 +303,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
rebuildAfterCacheVersionChanged[target] = true
|
||||
}
|
||||
|
||||
dataManager.getStorage(KotlinDataContainerTarget, LookupStorageProvider).clean()
|
||||
dataManager.getStorage(KotlinDataContainerTarget, JpsLookupStorageProvider).clean()
|
||||
return
|
||||
}
|
||||
CacheVersion.Action.REBUILD_CHUNK -> {
|
||||
@@ -333,7 +335,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
}
|
||||
CacheVersion.Action.CLEAN_DATA_CONTAINER -> {
|
||||
LOG.info("Clearing lookup cache")
|
||||
dataManager.getStorage(KotlinDataContainerTarget, LookupStorageProvider).clean()
|
||||
dataManager.getStorage(KotlinDataContainerTarget, JpsLookupStorageProvider).clean()
|
||||
cacheVersionsProvider.dataContainerVersion().clean()
|
||||
}
|
||||
else -> {
|
||||
@@ -353,7 +355,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
private fun doCompileModuleChunk(
|
||||
allCompiledFiles: MutableSet<File>, chunk: ModuleChunk, commonArguments: CommonCompilerArguments, context: CompileContext,
|
||||
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>, environment: CompilerEnvironment,
|
||||
filesToCompile: MultiMap<ModuleBuildTarget, File>, incrementalCaches: Map<ModuleBuildTarget, IncrementalCacheImpl>,
|
||||
filesToCompile: MultiMap<ModuleBuildTarget, File>, incrementalCaches: Map<ModuleBuildTarget, IncrementalCacheImpl<*>>,
|
||||
messageCollector: MessageCollectorAdapter, project: JpsProject
|
||||
): OutputItemsCollectorImpl? {
|
||||
|
||||
@@ -398,7 +400,8 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
context: CompileContext
|
||||
): CompilerEnvironment {
|
||||
val compilerServices = Services.Builder()
|
||||
.register(IncrementalCompilationComponents::class.java, IncrementalCompilationComponentsImpl(incrementalCaches, lookupTracker))
|
||||
.register(IncrementalCompilationComponents::class.java,
|
||||
IncrementalCompilationComponentsImpl(incrementalCaches.mapKeys { TargetId(it.key) }, lookupTracker))
|
||||
.register(CompilationCanceledStatus::class.java, object : CompilationCanceledStatus {
|
||||
override fun checkCanceled() {
|
||||
if (context.cancelStatus.isCanceled) throw CompilationCanceledException()
|
||||
@@ -424,7 +427,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
private fun getGeneratedFiles(
|
||||
chunk: ModuleChunk,
|
||||
outputItemCollector: OutputItemsCollectorImpl
|
||||
): List<GeneratedFile> {
|
||||
): List<GeneratedFile<ModuleBuildTarget>> {
|
||||
// If there's only one target, this map is empty: get() always returns null, and the representativeTarget will be used below
|
||||
val sourceToTarget = HashMap<File, ModuleBuildTarget>()
|
||||
if (chunk.targets.size > 1) {
|
||||
@@ -435,7 +438,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
}
|
||||
}
|
||||
|
||||
val result = ArrayList<GeneratedFile>()
|
||||
val result = ArrayList<GeneratedFile<ModuleBuildTarget>>()
|
||||
|
||||
val representativeTarget = chunk.representativeTarget()
|
||||
for (outputItem in outputItemCollector.outputs) {
|
||||
@@ -450,7 +453,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
result.add(GeneratedJvmClass(target, sourceFiles, outputFile))
|
||||
}
|
||||
else {
|
||||
result.add(GeneratedFile(target, sourceFiles, outputFile))
|
||||
result.add(GeneratedFile<ModuleBuildTarget>(target, sourceFiles, outputFile))
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -462,7 +465,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
context: CompileContext,
|
||||
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>,
|
||||
filesToCompile: MultiMap<ModuleBuildTarget, File>,
|
||||
generatedClasses: List<GeneratedJvmClass>
|
||||
generatedClasses: List<GeneratedJvmClass<ModuleBuildTarget>>
|
||||
) {
|
||||
val previousMappings = context.projectDescriptor.dataManager.mappings
|
||||
val delta = previousMappings.createDelta()
|
||||
@@ -481,7 +484,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
JavaBuilderUtil.updateMappings(context, delta, dirtyFilesHolder, chunk, allCompiled, compiledInThisRound)
|
||||
}
|
||||
|
||||
private fun registerOutputItems(outputConsumer: ModuleLevelBuilder.OutputConsumer, generatedFiles: List<GeneratedFile>) {
|
||||
private fun registerOutputItems(outputConsumer: ModuleLevelBuilder.OutputConsumer, generatedFiles: List<GeneratedFile<ModuleBuildTarget>>) {
|
||||
for (generatedFile in generatedFiles) {
|
||||
outputConsumer.registerOutputFile(generatedFile.target, generatedFile.outputFile, generatedFile.sourceFiles.map { it.path })
|
||||
}
|
||||
@@ -489,8 +492,8 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
|
||||
private fun updateKotlinIncrementalCache(
|
||||
compilationErrors: Boolean,
|
||||
incrementalCaches: Map<ModuleBuildTarget, IncrementalCacheImpl>,
|
||||
generatedFiles: List<GeneratedFile>
|
||||
incrementalCaches: Map<ModuleBuildTarget, JpsIncrementalCacheImpl>,
|
||||
generatedFiles: List<GeneratedFile<ModuleBuildTarget>>
|
||||
): CompilationResult {
|
||||
|
||||
assert(IncrementalCompilation.isEnabled()) { "updateKotlinIncrementalCache should not be called when incremental compilation disabled" }
|
||||
@@ -499,7 +502,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
for (generatedFile in generatedFiles) {
|
||||
val ic = incrementalCaches[generatedFile.target]!!
|
||||
val newChangesInfo =
|
||||
if (generatedFile is GeneratedJvmClass) {
|
||||
if (generatedFile is GeneratedJvmClass<ModuleBuildTarget>) {
|
||||
ic.saveFileToCache(generatedFile)
|
||||
}
|
||||
else if (generatedFile.outputFile.isModuleMappingFile()) {
|
||||
@@ -533,7 +536,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
|
||||
if (lookupTracker !is LookupTrackerImpl) throw AssertionError("Lookup tracker is expected to be LookupTrackerImpl, got ${lookupTracker.javaClass}")
|
||||
|
||||
val lookupStorage = dataManager.getStorage(KotlinDataContainerTarget, LookupStorageProvider)
|
||||
val lookupStorage = dataManager.getStorage(KotlinDataContainerTarget, JpsLookupStorageProvider)
|
||||
|
||||
filesToCompile.values().forEach { lookupStorage.removeLookupsFrom(it) }
|
||||
val removedFiles = chunk.targets.flatMap { KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, it) }
|
||||
@@ -542,8 +545,6 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) {
|
||||
lookupTracker.lookups.entrySet().forEach { lookupStorage.add(it.key, it.value) }
|
||||
}
|
||||
|
||||
private fun File.isModuleMappingFile() = extension == ModuleMapping.MAPPING_FILE_EXT && parentFile.name == "META-INF"
|
||||
|
||||
// if null is returned, nothing was done
|
||||
private fun compileToJs(chunk: ModuleChunk,
|
||||
commonArguments: CommonCompilerArguments,
|
||||
@@ -740,12 +741,11 @@ private fun CompilationResult.doProcessChangesUsingLookups(
|
||||
compiledFiles: Set<File>,
|
||||
dataManager: BuildDataManager,
|
||||
fsOperations: FSOperationsHelper,
|
||||
caches: Collection<IncrementalCacheImpl>
|
||||
caches: Collection<IncrementalCacheImpl<*>>
|
||||
) {
|
||||
val dirtyLookupSymbols = HashSet<LookupSymbol>()
|
||||
val lookupStorage = dataManager.getStorage(KotlinDataContainerTarget, LookupStorageProvider)
|
||||
val allCaches = caches.toHashSet()
|
||||
allCaches.addAll(caches.flatMap { it.dependentCaches })
|
||||
val lookupStorage = dataManager.getStorage(KotlinDataContainerTarget, JpsLookupStorageProvider)
|
||||
val allCaches: Sequence<IncrementalCacheImpl<*>> = caches.asSequence().flatMap { it.dependentsWithThis }
|
||||
|
||||
KotlinBuilder.LOG.debug("Start processing changes")
|
||||
|
||||
@@ -797,7 +797,7 @@ private fun CompilationResult.doProcessChangesUsingLookups(
|
||||
*/
|
||||
private fun withSubtypes(
|
||||
typeFqName: FqName,
|
||||
caches: Collection<IncrementalCacheImpl>
|
||||
caches: Sequence<IncrementalCacheImpl<*>>
|
||||
): Set<FqName> {
|
||||
val types = linkedListOf(typeFqName)
|
||||
val subtypes = hashSetOf<FqName>()
|
||||
@@ -805,10 +805,9 @@ private fun withSubtypes(
|
||||
while (types.isNotEmpty()) {
|
||||
val unprocessedType = types.pollFirst()
|
||||
|
||||
caches.asSequence()
|
||||
.flatMap { it.getSubtypesOf(unprocessedType) }
|
||||
.filter { it !in subtypes }
|
||||
.forEach { types.addLast(it) }
|
||||
caches.flatMap { it.getSubtypesOf(unprocessedType) }
|
||||
.filter { it !in subtypes }
|
||||
.forEach { types.addLast(it) }
|
||||
|
||||
subtypes.add(unprocessedType)
|
||||
}
|
||||
@@ -915,22 +914,6 @@ private fun hasKotlinDirtyOrRemovedFiles(
|
||||
return chunk.targets.any { KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, it).isNotEmpty() }
|
||||
}
|
||||
|
||||
open class GeneratedFile internal constructor(
|
||||
val target: ModuleBuildTarget,
|
||||
val sourceFiles: Collection<File>,
|
||||
val outputFile: File
|
||||
)
|
||||
|
||||
class GeneratedJvmClass (
|
||||
target: ModuleBuildTarget,
|
||||
sourceFiles: Collection<File>,
|
||||
outputFile: File
|
||||
) : GeneratedFile(target, sourceFiles, outputFile) {
|
||||
val outputClass = LocalFileKotlinClass.create(outputFile).sure {
|
||||
"Couldn't load KotlinClass from $outputFile; it may happen because class doesn't have valid Kotlin annotations"
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T, R> Iterable<T>.forAllPairs(other: Iterable<R>, fn: (T, R)->Unit) {
|
||||
for (t in this) {
|
||||
for (r in other) {
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.jps.build
|
||||
import org.jetbrains.jps.builders.storage.BuildDataPaths
|
||||
import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.jps.incremental.storage.BuildDataManager
|
||||
import org.jetbrains.kotlin.jps.incremental.KOTLIN_CACHE_DIRECTORY_NAME
|
||||
import org.jetbrains.kotlin.incremental.KOTLIN_CACHE_DIRECTORY_NAME
|
||||
import java.io.File
|
||||
|
||||
private val HAS_KOTLIN_MARKER_FILE_NAME = "has-kotlin-marker.txt"
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental
|
||||
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.storage.BuildDataPaths
|
||||
import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.jps.incremental.CacheVersion.Action
|
||||
import org.jetbrains.kotlin.load.java.JvmBytecodeBinaryVersion
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion
|
||||
import java.io.File
|
||||
|
||||
private val NORMAL_VERSION = 8
|
||||
private val EXPERIMENTAL_VERSION = 2
|
||||
private val DATA_CONTAINER_VERSION = 1
|
||||
|
||||
private val NORMAL_VERSION_FILE_NAME = "format-version.txt"
|
||||
private val EXPERIMENTAL_VERSION_FILE_NAME = "experimental-format-version.txt"
|
||||
private val DATA_CONTAINER_VERSION_FILE_NAME = "data-container-format-version.txt"
|
||||
|
||||
class CacheVersion(
|
||||
private val ownVersion: Int,
|
||||
private val versionFile: File,
|
||||
private val whenVersionChanged: CacheVersion.Action,
|
||||
private val whenTurnedOn: CacheVersion.Action,
|
||||
private val whenTurnedOff: CacheVersion.Action,
|
||||
isEnabled: ()->Boolean
|
||||
) {
|
||||
private val isEnabled by lazy(isEnabled)
|
||||
|
||||
private val actualVersion: Int
|
||||
get() = versionFile.readText().toInt()
|
||||
|
||||
private val expectedVersion: Int
|
||||
get() {
|
||||
val metadata = JvmMetadataVersion.INSTANCE
|
||||
val bytecode = JvmBytecodeBinaryVersion.INSTANCE
|
||||
return ownVersion * 1000000 +
|
||||
bytecode.major * 10000 + bytecode.minor * 100 +
|
||||
metadata.major * 1000 + metadata.minor
|
||||
}
|
||||
|
||||
fun checkVersion(): Action =
|
||||
when (versionFile.exists() to isEnabled) {
|
||||
true to true -> if (actualVersion != expectedVersion) whenVersionChanged else Action.DO_NOTHING
|
||||
false to true -> whenTurnedOn
|
||||
true to false -> whenTurnedOff
|
||||
else -> Action.DO_NOTHING
|
||||
}
|
||||
|
||||
fun saveIfNeeded() {
|
||||
if (!isEnabled) return
|
||||
|
||||
if (!versionFile.parentFile.exists()) {
|
||||
versionFile.parentFile.mkdirs()
|
||||
}
|
||||
|
||||
versionFile.writeText(expectedVersion.toString())
|
||||
}
|
||||
|
||||
fun clean() {
|
||||
versionFile.delete()
|
||||
}
|
||||
|
||||
@get:TestOnly
|
||||
val formatVersionFile: File
|
||||
get() = versionFile
|
||||
|
||||
// Order of entries is important, because actions are sorted in KotlinBuilder::checkVersions
|
||||
enum class Action {
|
||||
REBUILD_ALL_KOTLIN,
|
||||
REBUILD_CHUNK,
|
||||
CLEAN_NORMAL_CACHES,
|
||||
CLEAN_EXPERIMENTAL_CACHES,
|
||||
CLEAN_DATA_CONTAINER,
|
||||
DO_NOTHING
|
||||
}
|
||||
}
|
||||
|
||||
class CacheVersionProvider(private val paths: BuildDataPaths) {
|
||||
private val BuildTarget<*>.dataRoot: File
|
||||
get() = paths.getTargetDataRoot(this)
|
||||
|
||||
fun normalVersion(target: ModuleBuildTarget): CacheVersion =
|
||||
CacheVersion(ownVersion = NORMAL_VERSION,
|
||||
versionFile = File(target.dataRoot, NORMAL_VERSION_FILE_NAME),
|
||||
whenVersionChanged = Action.REBUILD_CHUNK,
|
||||
whenTurnedOn = Action.REBUILD_CHUNK,
|
||||
whenTurnedOff = Action.CLEAN_NORMAL_CACHES,
|
||||
isEnabled = { IncrementalCompilation.isEnabled() })
|
||||
|
||||
fun experimentalVersion(target: ModuleBuildTarget): CacheVersion =
|
||||
CacheVersion(ownVersion = EXPERIMENTAL_VERSION,
|
||||
versionFile = File(target.dataRoot, EXPERIMENTAL_VERSION_FILE_NAME),
|
||||
whenVersionChanged = Action.REBUILD_CHUNK,
|
||||
whenTurnedOn = Action.REBUILD_CHUNK,
|
||||
whenTurnedOff = Action.CLEAN_EXPERIMENTAL_CACHES,
|
||||
isEnabled = { IncrementalCompilation.isExperimental() })
|
||||
|
||||
fun dataContainerVersion(): CacheVersion =
|
||||
CacheVersion(ownVersion = DATA_CONTAINER_VERSION,
|
||||
versionFile = File(KotlinDataContainerTarget.dataRoot, DATA_CONTAINER_VERSION_FILE_NAME),
|
||||
whenVersionChanged = Action.REBUILD_ALL_KOTLIN,
|
||||
whenTurnedOn = Action.REBUILD_ALL_KOTLIN,
|
||||
whenTurnedOff = Action.CLEAN_DATA_CONTAINER,
|
||||
isEnabled = { IncrementalCompilation.isExperimental() })
|
||||
|
||||
fun allVersions(targets: Iterable<ModuleBuildTarget>): Iterable<CacheVersion> {
|
||||
val versions = arrayListOf<CacheVersion>()
|
||||
versions.add(dataContainerVersion())
|
||||
|
||||
for (target in targets) {
|
||||
versions.add(normalVersion(target))
|
||||
versions.add(experimentalVersion(target))
|
||||
}
|
||||
|
||||
return versions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.jps.incremental
|
||||
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.storage.BuildDataPaths
|
||||
import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.kotlin.incremental.*
|
||||
import java.io.File
|
||||
|
||||
|
||||
class CacheVersionProvider(private val paths: BuildDataPaths) {
|
||||
private val BuildTarget<*>.dataRoot: File
|
||||
get() = paths.getTargetDataRoot(this)
|
||||
|
||||
fun normalVersion(target: ModuleBuildTarget): CacheVersion = normalCacheVersion(target.dataRoot)
|
||||
|
||||
fun experimentalVersion(target: ModuleBuildTarget): CacheVersion = experimentalCacheVersion(target.dataRoot)
|
||||
|
||||
fun dataContainerVersion(): CacheVersion = dataContainerCacheVersion(KotlinDataContainerTarget.dataRoot)
|
||||
|
||||
fun allVersions(targets: Iterable<ModuleBuildTarget>): Iterable<CacheVersion> = allCachesVersions(KotlinDataContainerTarget.dataRoot, targets.map { it.dataRoot } )
|
||||
}
|
||||
@@ -1,652 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental
|
||||
|
||||
import com.google.protobuf.MessageLite
|
||||
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.io.BooleanDataDescriptor
|
||||
import com.intellij.util.io.EnumeratorStringDescriptor
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.jps.builders.storage.BuildDataPaths
|
||||
import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.jps.incremental.storage.PathStringDescriptor
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.jps.build.GeneratedJvmClass
|
||||
import org.jetbrains.kotlin.jps.build.KotlinBuilder
|
||||
import org.jetbrains.kotlin.jps.incremental.storage.*
|
||||
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
|
||||
import org.jetbrains.kotlin.serialization.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.serialization.deserialization.supertypes
|
||||
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.FieldVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
val KOTLIN_CACHE_DIRECTORY_NAME = "kotlin"
|
||||
|
||||
open class IncrementalCacheImpl(
|
||||
private val target: ModuleBuildTarget,
|
||||
paths: BuildDataPaths
|
||||
) : BasicMapsOwner(), IncrementalCache {
|
||||
companion object {
|
||||
private val PROTO_MAP = "proto"
|
||||
private val CONSTANTS_MAP = "constants"
|
||||
private val PACKAGE_PARTS = "package-parts"
|
||||
private val MULTIFILE_CLASS_FACADES = "multifile-class-facades"
|
||||
private val MULTIFILE_CLASS_PARTS = "multifile-class-parts"
|
||||
private val SOURCE_TO_CLASSES = "source-to-classes"
|
||||
private val DIRTY_OUTPUT_CLASSES = "dirty-output-classes"
|
||||
private val SUBTYPES = "subtypes"
|
||||
private val SUPERTYPES = "supertypes"
|
||||
|
||||
private val MODULE_MAPPING_FILE_NAME = "." + ModuleMapping.MAPPING_FILE_EXT
|
||||
}
|
||||
|
||||
private val baseDir = File(paths.getTargetDataRoot(target), KOTLIN_CACHE_DIRECTORY_NAME)
|
||||
private val cacheVersionProvider = CacheVersionProvider(paths)
|
||||
private val experimentalMaps = arrayListOf<BasicMap<*, *>>()
|
||||
|
||||
private fun <K, V, M : BasicMap<K, V>> registerExperimentalMap(map: M): M {
|
||||
experimentalMaps.add(map)
|
||||
return registerMap(map)
|
||||
}
|
||||
|
||||
protected val String.storageFile: File
|
||||
get() = File(baseDir, this + "." + CACHE_EXTENSION)
|
||||
|
||||
private val protoMap = registerMap(ProtoMap(PROTO_MAP.storageFile))
|
||||
private val constantsMap = registerMap(ConstantsMap(CONSTANTS_MAP.storageFile))
|
||||
private val packagePartMap = registerMap(PackagePartMap(PACKAGE_PARTS.storageFile))
|
||||
private val multifileFacadeToParts = registerMap(MultifileClassFacadeMap(MULTIFILE_CLASS_FACADES.storageFile))
|
||||
private val partToMultifileFacade = registerMap(MultifileClassPartMap(MULTIFILE_CLASS_PARTS.storageFile))
|
||||
private val sourceToClassesMap = registerMap(SourceToClassesMap(SOURCE_TO_CLASSES.storageFile))
|
||||
private val dirtyOutputClassesMap = registerMap(DirtyOutputClassesMap(DIRTY_OUTPUT_CLASSES.storageFile))
|
||||
private val subtypesMap = registerExperimentalMap(SubtypesMap(SUBTYPES.storageFile))
|
||||
private val supertypesMap = registerExperimentalMap(SupertypesMap(SUPERTYPES.storageFile))
|
||||
|
||||
private val dependents = arrayListOf<IncrementalCacheImpl>()
|
||||
private val outputDir by lazy(LazyThreadSafetyMode.NONE) { requireNotNull(target.outputDir) { "Target is expected to have output directory: $target" } }
|
||||
|
||||
protected val dependentsWithThis: Sequence<IncrementalCacheImpl>
|
||||
get() = sequenceOf(this).plus(dependents.asSequence())
|
||||
|
||||
internal val dependentCaches: Iterable<IncrementalCacheImpl>
|
||||
get() = dependents
|
||||
|
||||
override fun registerInline(fromPath: String, jvmSignature: String, toPath: String) {
|
||||
}
|
||||
|
||||
fun addDependentCache(cache: IncrementalCacheImpl) {
|
||||
dependents.add(cache)
|
||||
}
|
||||
|
||||
fun markOutputClassesDirty(removedAndCompiledSources: List<File>) {
|
||||
for (sourceFile in removedAndCompiledSources) {
|
||||
val classes = sourceToClassesMap[sourceFile]
|
||||
classes.forEach {
|
||||
dirtyOutputClassesMap.markDirty(it.internalName)
|
||||
}
|
||||
|
||||
sourceToClassesMap.clearOutputsForSource(sourceFile)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSubtypesOf(className: FqName): Sequence<FqName> =
|
||||
subtypesMap[className].asSequence()
|
||||
|
||||
override fun getClassFilePath(internalClassName: String): String {
|
||||
return toSystemIndependentName(File(outputDir, "$internalClassName.class").canonicalPath)
|
||||
}
|
||||
|
||||
fun saveModuleMappingToCache(sourceFiles: Collection<File>, file: File): CompilationResult {
|
||||
val jvmClassName = JvmClassName.byInternalName(MODULE_MAPPING_FILE_NAME)
|
||||
protoMap.process(jvmClassName, file.readBytes(), emptyArray<String>(), isPackage = false, checkChangesIsOpenPart = false)
|
||||
dirtyOutputClassesMap.notDirty(MODULE_MAPPING_FILE_NAME)
|
||||
sourceFiles.forEach { sourceToClassesMap.add(it, jvmClassName) }
|
||||
return CompilationResult.NO_CHANGES
|
||||
}
|
||||
|
||||
fun saveFileToCache(generatedClass: GeneratedJvmClass): CompilationResult {
|
||||
val sourceFiles: Collection<File> = generatedClass.sourceFiles
|
||||
val kotlinClass: LocalFileKotlinClass = generatedClass.outputClass
|
||||
val className = kotlinClass.className
|
||||
|
||||
dirtyOutputClassesMap.notDirty(className.internalName)
|
||||
sourceFiles.forEach {
|
||||
sourceToClassesMap.add(it, className)
|
||||
}
|
||||
|
||||
if (kotlinClass.classId.isLocal) {
|
||||
return CompilationResult.NO_CHANGES
|
||||
}
|
||||
|
||||
val header = kotlinClass.classHeader
|
||||
val changesInfo = when (header.kind) {
|
||||
KotlinClassHeader.Kind.FILE_FACADE -> {
|
||||
assert(sourceFiles.size == 1) { "Package part from several source files: $sourceFiles" }
|
||||
packagePartMap.addPackagePart(className)
|
||||
|
||||
protoMap.process(kotlinClass, isPackage = true) +
|
||||
constantsMap.process(kotlinClass) +
|
||||
additionalProcessChangedClass(kotlinClass, isPackage = true)
|
||||
}
|
||||
KotlinClassHeader.Kind.MULTIFILE_CLASS -> {
|
||||
val partNames = kotlinClass.classHeader.data?.toList()
|
||||
?: throw AssertionError("Multifile class has no parts: ${kotlinClass.className}")
|
||||
multifileFacadeToParts[className] = partNames
|
||||
// When a class is replaced with a facade with the same name,
|
||||
// the class' proto wouldn't ever be deleted,
|
||||
// because we don't write proto for multifile facades.
|
||||
// As a workaround we can remove proto values for multifile facades.
|
||||
protoMap.remove(className)
|
||||
|
||||
// TODO NO_CHANGES? (delegates only)
|
||||
constantsMap.process(kotlinClass) +
|
||||
additionalProcessChangedClass(kotlinClass, isPackage = true)
|
||||
}
|
||||
KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> {
|
||||
assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" }
|
||||
packagePartMap.addPackagePart(className)
|
||||
partToMultifileFacade.set(className.internalName, header.multifileClassName!!)
|
||||
|
||||
protoMap.process(kotlinClass, isPackage = true) +
|
||||
constantsMap.process(kotlinClass) +
|
||||
additionalProcessChangedClass(kotlinClass, isPackage = true)
|
||||
}
|
||||
KotlinClassHeader.Kind.CLASS -> {
|
||||
addToClassStorage(kotlinClass)
|
||||
|
||||
protoMap.process(kotlinClass, isPackage = false) +
|
||||
constantsMap.process(kotlinClass) +
|
||||
additionalProcessChangedClass(kotlinClass, isPackage = false)
|
||||
}
|
||||
else -> CompilationResult.NO_CHANGES
|
||||
}
|
||||
|
||||
changesInfo.logIfSomethingChanged(className)
|
||||
return changesInfo
|
||||
}
|
||||
|
||||
protected open fun additionalProcessChangedClass(kotlinClass: LocalFileKotlinClass, isPackage: Boolean) = CompilationResult.NO_CHANGES
|
||||
|
||||
private fun CompilationResult.logIfSomethingChanged(className: JvmClassName) {
|
||||
if (this == CompilationResult.NO_CHANGES) return
|
||||
|
||||
KotlinBuilder.LOG.debug("$className is changed: $this")
|
||||
}
|
||||
|
||||
fun clearCacheForRemovedClasses(): CompilationResult {
|
||||
|
||||
fun <T> T.getNonPrivateNames(nameResolver: NameResolver, vararg members: T.() -> List<MessageLite>): Set<String> =
|
||||
members.flatMap { this.it().filterNot { it.isPrivate }.names(nameResolver) }.toSet()
|
||||
|
||||
fun createChangeInfo(className: JvmClassName): ChangeInfo? {
|
||||
if (className.internalName == MODULE_MAPPING_FILE_NAME) return null
|
||||
|
||||
val mapValue = protoMap.get(className) ?: return null
|
||||
|
||||
return when {
|
||||
mapValue.isPackageFacade -> {
|
||||
val packageData = JvmProtoBufUtil.readPackageDataFrom(mapValue.bytes, mapValue.strings)
|
||||
|
||||
val memberNames =
|
||||
packageData.packageProto.getNonPrivateNames(
|
||||
packageData.nameResolver,
|
||||
ProtoBuf.Package::getFunctionList,
|
||||
ProtoBuf.Package::getPropertyList
|
||||
)
|
||||
|
||||
ChangeInfo.Removed(className.packageFqName, memberNames)
|
||||
}
|
||||
else -> {
|
||||
val classData = JvmProtoBufUtil.readClassDataFrom(mapValue.bytes, mapValue.strings)
|
||||
|
||||
val memberNames =
|
||||
classData.classProto.getNonPrivateNames(
|
||||
classData.nameResolver,
|
||||
ProtoBuf.Class::getConstructorList,
|
||||
ProtoBuf.Class::getFunctionList,
|
||||
ProtoBuf.Class::getPropertyList
|
||||
) + classData.classProto.enumEntryList.map { classData.nameResolver.getString(it.name) }
|
||||
|
||||
ChangeInfo.Removed(className.fqNameForClassNameWithoutDollars, memberNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dirtyClasses = dirtyOutputClassesMap
|
||||
.getDirtyOutputClasses()
|
||||
.map(JvmClassName::byInternalName)
|
||||
.toList()
|
||||
|
||||
val changes =
|
||||
if (IncrementalCompilation.isExperimental())
|
||||
dirtyClasses.mapNotNull { createChangeInfo(it) }.asSequence()
|
||||
else
|
||||
emptySequence<ChangeInfo>()
|
||||
|
||||
val changesInfo = dirtyClasses.fold(CompilationResult(changes = changes)) { info, className ->
|
||||
val newInfo = CompilationResult(protoChanged = className in protoMap,
|
||||
constantsChanged = className in constantsMap)
|
||||
newInfo.logIfSomethingChanged(className)
|
||||
info + newInfo
|
||||
}
|
||||
|
||||
val facadesWithRemovedParts = hashMapOf<JvmClassName, MutableSet<String>>()
|
||||
for (dirtyClass in dirtyClasses) {
|
||||
val facade = partToMultifileFacade.get(dirtyClass.internalName) ?: continue
|
||||
val facadeClassName = JvmClassName.byInternalName(facade)
|
||||
val removedParts = facadesWithRemovedParts.getOrPut(facadeClassName) { hashSetOf() }
|
||||
removedParts.add(dirtyClass.internalName)
|
||||
}
|
||||
|
||||
for ((facade, removedParts) in facadesWithRemovedParts.entries) {
|
||||
val allParts = multifileFacadeToParts[facade.internalName] ?: continue
|
||||
val notRemovedParts = allParts.filter { it !in removedParts }
|
||||
|
||||
if (notRemovedParts.isEmpty()) {
|
||||
multifileFacadeToParts.remove(facade)
|
||||
}
|
||||
else {
|
||||
multifileFacadeToParts[facade] = notRemovedParts
|
||||
}
|
||||
}
|
||||
|
||||
dirtyClasses.forEach {
|
||||
protoMap.remove(it)
|
||||
packagePartMap.remove(it)
|
||||
multifileFacadeToParts.remove(it)
|
||||
partToMultifileFacade.remove(it)
|
||||
constantsMap.remove(it)
|
||||
}
|
||||
|
||||
additionalProcessRemovedClasses(dirtyClasses)
|
||||
|
||||
removeAllFromClassStorage(dirtyClasses)
|
||||
|
||||
dirtyOutputClassesMap.clean()
|
||||
return changesInfo
|
||||
}
|
||||
|
||||
protected open fun additionalProcessRemovedClasses(dirtyClasses: List<JvmClassName>) {
|
||||
}
|
||||
|
||||
override fun getObsoletePackageParts(): Collection<String> {
|
||||
val obsoletePackageParts =
|
||||
dirtyOutputClassesMap.getDirtyOutputClasses().filter { packagePartMap.isPackagePart(JvmClassName.byInternalName(it)) }
|
||||
KotlinBuilder.LOG.debug("Obsolete package parts: ${obsoletePackageParts}")
|
||||
return obsoletePackageParts
|
||||
}
|
||||
|
||||
override fun getPackagePartData(partInternalName: String): JvmPackagePartProto? {
|
||||
return protoMap[JvmClassName.byInternalName(partInternalName)]?.let { value ->
|
||||
JvmPackagePartProto(value.bytes, value.strings)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getObsoleteMultifileClasses(): Collection<String> {
|
||||
val obsoleteMultifileClasses = linkedSetOf<String>()
|
||||
for (dirtyClass in dirtyOutputClassesMap.getDirtyOutputClasses()) {
|
||||
val dirtyFacade = partToMultifileFacade.get(dirtyClass) ?: continue
|
||||
obsoleteMultifileClasses.add(dirtyFacade)
|
||||
}
|
||||
KotlinBuilder.LOG.debug("Obsolete multifile class facades: $obsoleteMultifileClasses")
|
||||
return obsoleteMultifileClasses
|
||||
}
|
||||
|
||||
override fun getStableMultifileFacadeParts(facadeInternalName: String): Collection<String>? {
|
||||
val partNames = multifileFacadeToParts.get(facadeInternalName) ?: return null
|
||||
return partNames.filter { !dirtyOutputClassesMap.isDirty(it) }
|
||||
}
|
||||
|
||||
override fun getMultifileFacade(partInternalName: String): String? {
|
||||
return partToMultifileFacade.get(partInternalName)
|
||||
}
|
||||
|
||||
override fun getModuleMappingData(): ByteArray? {
|
||||
return protoMap[JvmClassName.byInternalName(MODULE_MAPPING_FILE_NAME)]?.bytes
|
||||
}
|
||||
|
||||
override fun clean() {
|
||||
super.clean()
|
||||
cacheVersionProvider.normalVersion(target).clean()
|
||||
cacheVersionProvider.experimentalVersion(target).clean()
|
||||
}
|
||||
|
||||
fun cleanExperimental() {
|
||||
cacheVersionProvider.experimentalVersion(target).clean()
|
||||
experimentalMaps.forEach { it.clean() }
|
||||
}
|
||||
|
||||
private inner class ProtoMap(storageFile: File) : BasicStringMap<ProtoMapValue>(storageFile, ProtoMapValueExternalizer) {
|
||||
|
||||
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult {
|
||||
val header = kotlinClass.classHeader
|
||||
val bytes = BitEncoding.decodeBytes(header.data!!)
|
||||
return put(kotlinClass.className, bytes, header.strings!!, isPackage, checkChangesIsOpenPart = true)
|
||||
}
|
||||
|
||||
fun process(className: JvmClassName, data: ByteArray, strings: Array<String>, isPackage: Boolean, checkChangesIsOpenPart: Boolean): CompilationResult {
|
||||
return put(className, data, strings, isPackage, checkChangesIsOpenPart)
|
||||
}
|
||||
|
||||
private fun put(
|
||||
className: JvmClassName, bytes: ByteArray, strings: Array<String>, isPackage: Boolean, checkChangesIsOpenPart: Boolean
|
||||
): CompilationResult {
|
||||
val key = className.internalName
|
||||
val oldData = storage[key]
|
||||
val data = ProtoMapValue(isPackage, bytes, strings)
|
||||
|
||||
if (oldData == null ||
|
||||
!Arrays.equals(bytes, oldData.bytes) ||
|
||||
!Arrays.equals(strings, oldData.strings) ||
|
||||
isPackage != oldData.isPackageFacade
|
||||
) {
|
||||
storage[key] = data
|
||||
}
|
||||
|
||||
if (oldData == null || !checkChangesIsOpenPart) return CompilationResult(protoChanged = true)
|
||||
|
||||
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(protoChanged = changeList.isNotEmpty(), changes = changeList.asSequence())
|
||||
}
|
||||
|
||||
operator fun contains(className: JvmClassName): Boolean =
|
||||
className.internalName in storage
|
||||
|
||||
operator fun get(className: JvmClassName): ProtoMapValue? =
|
||||
storage[className.internalName]
|
||||
|
||||
fun remove(className: JvmClassName) {
|
||||
storage.remove(className.internalName)
|
||||
}
|
||||
|
||||
override fun dumpValue(value: ProtoMapValue): String {
|
||||
return (if (value.isPackageFacade) "1" else "0") + java.lang.Long.toHexString(value.bytes.md5())
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ConstantsMap(storageFile: File) : BasicStringMap<Map<String, Any>>(storageFile, ConstantsMapExternalizer) {
|
||||
private fun getConstantsMap(bytes: ByteArray): Map<String, Any>? {
|
||||
val result = HashMap<String, Any>()
|
||||
|
||||
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5) {
|
||||
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
|
||||
val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL or Opcodes.ACC_PRIVATE
|
||||
if (value != null && access and staticFinal == Opcodes.ACC_STATIC or Opcodes.ACC_FINAL) {
|
||||
result[name] = value
|
||||
}
|
||||
return null
|
||||
}
|
||||
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
|
||||
|
||||
return if (result.isEmpty()) null else result
|
||||
}
|
||||
|
||||
operator fun contains(className: JvmClassName): Boolean =
|
||||
className.internalName in storage
|
||||
|
||||
fun process(kotlinClass: LocalFileKotlinClass): CompilationResult {
|
||||
return put(kotlinClass.className, getConstantsMap(kotlinClass.fileContents))
|
||||
}
|
||||
|
||||
private fun put(className: JvmClassName, constantsMap: Map<String, Any>?): CompilationResult {
|
||||
val key = className.internalName
|
||||
|
||||
val oldMap = storage[key]
|
||||
if (oldMap == constantsMap) return CompilationResult.NO_CHANGES
|
||||
|
||||
if (constantsMap != null) {
|
||||
storage[key] = constantsMap
|
||||
}
|
||||
else {
|
||||
storage.remove(key)
|
||||
}
|
||||
|
||||
return CompilationResult(constantsChanged = true)
|
||||
}
|
||||
|
||||
fun remove(className: JvmClassName) {
|
||||
put(className, null)
|
||||
}
|
||||
|
||||
override fun dumpValue(value: Map<String, Any>): String =
|
||||
value.dumpMap(Any::toString)
|
||||
}
|
||||
|
||||
private inner class PackagePartMap(storageFile: File) : BasicStringMap<Boolean>(storageFile, BooleanDataDescriptor.INSTANCE) {
|
||||
fun addPackagePart(className: JvmClassName) {
|
||||
storage[className.internalName] = true
|
||||
}
|
||||
|
||||
fun remove(className: JvmClassName) {
|
||||
storage.remove(className.internalName)
|
||||
}
|
||||
|
||||
fun isPackagePart(className: JvmClassName): Boolean =
|
||||
className.internalName in storage
|
||||
|
||||
override fun dumpValue(value: Boolean) = ""
|
||||
}
|
||||
|
||||
private inner class MultifileClassFacadeMap(storageFile: File) : BasicStringMap<Collection<String>>(storageFile, StringCollectionExternalizer) {
|
||||
operator fun set(facadeName: JvmClassName, partNames: Collection<String>) {
|
||||
storage[facadeName.internalName] = partNames
|
||||
}
|
||||
|
||||
operator fun get(facadeName: String): Collection<String>? = storage[facadeName]
|
||||
|
||||
fun remove(className: JvmClassName) {
|
||||
storage.remove(className.internalName)
|
||||
}
|
||||
|
||||
override fun dumpValue(value: Collection<String>): String = value.dumpCollection()
|
||||
}
|
||||
|
||||
private inner class MultifileClassPartMap(storageFile: File) : BasicStringMap<String>(storageFile, EnumeratorStringDescriptor.INSTANCE) {
|
||||
fun set(partName: String, facadeName: String) {
|
||||
storage[partName] = facadeName
|
||||
}
|
||||
|
||||
fun get(partName: String): String? {
|
||||
return storage.get(partName)
|
||||
}
|
||||
|
||||
fun remove(className: JvmClassName) {
|
||||
storage.remove(className.internalName)
|
||||
}
|
||||
|
||||
override fun dumpValue(value: String): String = value
|
||||
}
|
||||
|
||||
private inner class SourceToClassesMap(storageFile: File) : BasicStringMap<Collection<String>>(storageFile, PathStringDescriptor.INSTANCE, StringCollectionExternalizer) {
|
||||
fun clearOutputsForSource(sourceFile: File) {
|
||||
remove(sourceFile.absolutePath)
|
||||
}
|
||||
|
||||
fun add(sourceFile: File, className: JvmClassName) {
|
||||
storage.append(sourceFile.absolutePath, className.internalName)
|
||||
}
|
||||
|
||||
operator fun get(sourceFile: File): Collection<JvmClassName> =
|
||||
storage[sourceFile.absolutePath].orEmpty().map { JvmClassName.byInternalName(it) }
|
||||
|
||||
override fun dumpValue(value: Collection<String>) = value.dumpCollection()
|
||||
|
||||
override fun clean() {
|
||||
storage.keys.forEach { remove(it) }
|
||||
}
|
||||
|
||||
private fun remove(path: String) {
|
||||
storage.remove(path)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToClassStorage(kotlinClass: LocalFileKotlinClass) {
|
||||
if (!IncrementalCompilation.isExperimental()) return
|
||||
|
||||
val classData = JvmProtoBufUtil.readClassDataFrom(kotlinClass.classHeader.data!!, kotlinClass.classHeader.strings!!)
|
||||
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
|
||||
}
|
||||
|
||||
private fun removeAllFromClassStorage(removedClasses: Collection<JvmClassName>) {
|
||||
if (!IncrementalCompilation.isExperimental() || removedClasses.isEmpty()) return
|
||||
|
||||
val removedFqNames = removedClasses.map { it.fqNameForClassNameWithoutDollars }.toSet()
|
||||
|
||||
for (cache in dependentsWithThis) {
|
||||
val parentsFqNames = hashSetOf<FqName>()
|
||||
val childrenFqNames = hashSetOf<FqName>()
|
||||
|
||||
for (removedFqName in removedFqNames) {
|
||||
parentsFqNames.addAll(cache.supertypesMap[removedFqName])
|
||||
childrenFqNames.addAll(cache.subtypesMap[removedFqName])
|
||||
|
||||
cache.supertypesMap.remove(removedFqName)
|
||||
cache.subtypesMap.remove(removedFqName)
|
||||
}
|
||||
|
||||
for (child in childrenFqNames) {
|
||||
cache.supertypesMap.removeValues(child, removedFqNames)
|
||||
}
|
||||
|
||||
for (parent in parentsFqNames) {
|
||||
cache.subtypesMap.removeValues(parent, removedFqNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class DirtyOutputClassesMap(storageFile: File) : BasicStringMap<Boolean>(storageFile, BooleanDataDescriptor.INSTANCE) {
|
||||
fun markDirty(className: String) {
|
||||
storage[className] = true
|
||||
}
|
||||
|
||||
fun notDirty(className: String) {
|
||||
storage.remove(className)
|
||||
}
|
||||
|
||||
fun getDirtyOutputClasses(): Collection<String> =
|
||||
storage.keys
|
||||
|
||||
fun isDirty(className: String): Boolean =
|
||||
storage.contains(className)
|
||||
|
||||
override fun dumpValue(value: Boolean) = ""
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ChangeInfo(val fqName: FqName) {
|
||||
open class MembersChanged(fqName: FqName, val names: Collection<String>) : ChangeInfo(fqName) {
|
||||
override fun toStringProperties(): String = super.toStringProperties() + ", names = $names"
|
||||
}
|
||||
|
||||
class Removed(fqName: FqName, names: Collection<String>) : MembersChanged(fqName, names)
|
||||
|
||||
class SignatureChanged(fqName: FqName, val areSubclassesAffected: Boolean) : ChangeInfo(fqName)
|
||||
|
||||
|
||||
protected open fun toStringProperties(): String = "fqName = $fqName"
|
||||
|
||||
override fun toString(): String {
|
||||
return this.javaClass.simpleName + "(${toStringProperties()})"
|
||||
}
|
||||
}
|
||||
|
||||
data class CompilationResult(
|
||||
val protoChanged: Boolean = false,
|
||||
val constantsChanged: Boolean = false,
|
||||
val inlineChanged: Boolean = false,
|
||||
val inlineAdded: Boolean = false,
|
||||
val changes: Sequence<ChangeInfo> = emptySequence()
|
||||
) {
|
||||
companion object {
|
||||
val NO_CHANGES: CompilationResult = CompilationResult()
|
||||
}
|
||||
|
||||
operator fun plus(other: CompilationResult): CompilationResult =
|
||||
CompilationResult(protoChanged || other.protoChanged,
|
||||
constantsChanged || other.constantsChanged,
|
||||
inlineChanged || other.inlineChanged,
|
||||
inlineAdded || other.inlineAdded,
|
||||
changes + other.changes)
|
||||
}
|
||||
|
||||
fun ByteArray.md5(): Long {
|
||||
val d = MessageDigest.getInstance("MD5").digest(this)!!
|
||||
return ((d[0].toLong() and 0xFFL)
|
||||
or ((d[1].toLong() and 0xFFL) shl 8)
|
||||
or ((d[2].toLong() and 0xFFL) shl 16)
|
||||
or ((d[3].toLong() and 0xFFL) shl 24)
|
||||
or ((d[4].toLong() and 0xFFL) shl 32)
|
||||
or ((d[5].toLong() and 0xFFL) shl 40)
|
||||
or ((d[6].toLong() and 0xFFL) shl 48)
|
||||
or ((d[7].toLong() and 0xFFL) shl 56)
|
||||
)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun <K : Comparable<K>, V> Map<K, V>.dumpMap(dumpValue: (V)->String): String =
|
||||
buildString {
|
||||
append("{")
|
||||
for (key in keys.sorted()) {
|
||||
if (length != 1) {
|
||||
append(", ")
|
||||
}
|
||||
|
||||
val value = get(key)?.let(dumpValue) ?: "null"
|
||||
append("$key -> $value")
|
||||
}
|
||||
append("}")
|
||||
}
|
||||
|
||||
@TestOnly fun <T : Comparable<T>> Collection<T>.dumpCollection(): String =
|
||||
"[${sorted().joinToString(", ", transform = Any::toString)}]"
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental
|
||||
|
||||
import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
|
||||
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
|
||||
import org.jetbrains.kotlin.modules.TargetId
|
||||
|
||||
class IncrementalCompilationComponentsImpl(
|
||||
caches: Map<ModuleBuildTarget, IncrementalCache>,
|
||||
private val lookupTracker: LookupTracker
|
||||
): IncrementalCompilationComponents {
|
||||
private val caches = caches.mapKeys { TargetId(it.key) }
|
||||
|
||||
override fun getIncrementalCache(target: TargetId): IncrementalCache =
|
||||
caches[target]!!
|
||||
|
||||
override fun getLookupTracker(): LookupTracker = lookupTracker
|
||||
}
|
||||
@@ -24,8 +24,10 @@ import org.jetbrains.jps.incremental.ModuleBuildTarget
|
||||
import org.jetbrains.jps.incremental.storage.BuildDataManager
|
||||
import org.jetbrains.jps.incremental.storage.StorageOwner
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.incremental.*
|
||||
import org.jetbrains.kotlin.incremental.storage.*
|
||||
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
|
||||
import org.jetbrains.kotlin.jps.incremental.storage.*
|
||||
import org.jetbrains.kotlin.jps.build.KotlinBuilder
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import java.io.File
|
||||
@@ -34,7 +36,7 @@ import java.util.*
|
||||
class JpsIncrementalCacheImpl(
|
||||
target: ModuleBuildTarget,
|
||||
paths: BuildDataPaths
|
||||
) : IncrementalCacheImpl(target, paths), StorageOwner {
|
||||
) : IncrementalCacheImpl<ModuleBuildTarget>(paths.getTargetDataRoot(target), target.outputDir, target), StorageOwner {
|
||||
|
||||
private val inlineFunctionsMap = registerMap(InlineFunctionsMap(INLINE_FUNCTIONS.storageFile))
|
||||
private val dirtyInlineFunctionsMap = registerMap(DirtyInlineFunctionsMap(DIRTY_INLINE_FUNCTIONS.storageFile))
|
||||
@@ -46,6 +48,10 @@ class JpsIncrementalCacheImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun debugLog(message: String) {
|
||||
KotlinBuilder.LOG.debug(message)
|
||||
}
|
||||
|
||||
override fun additionalProcessChangedClass(kotlinClass: LocalFileKotlinClass, isPackage: Boolean) =
|
||||
inlineFunctionsMap.process(kotlinClass, isPackage)
|
||||
|
||||
@@ -173,7 +179,7 @@ class JpsIncrementalCacheImpl(
|
||||
* * inlineFunction - jvmSignature of some inline function in source file
|
||||
* * target files - collection of files inlineFunction has been inlined to
|
||||
*/
|
||||
private inner class InlineFunctionsFilesMap(storageFile: File) : BasicMap<PathFunctionPair, Collection<String>>(storageFile, PathFunctionPairKeyDescriptor, PathCollectionExternalizer) {
|
||||
private inner class InlineFunctionsFilesMap(storageFile: File) : BasicMap<PathFunctionPair, Collection<String>>(storageFile, PathFunctionPairKeyDescriptor, PathStringCollectionExternalizer) {
|
||||
fun add(sourcePath: String, jvmSignature: String, targetPath: String) {
|
||||
val key = PathFunctionPair(sourcePath, jvmSignature)
|
||||
storage.append(key, targetPath)
|
||||
|
||||
+8
-18
@@ -14,25 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.jps.incremental.storage
|
||||
package org.jetbrains.kotlin.jps.incremental
|
||||
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import org.jetbrains.jps.builders.storage.StorageProvider
|
||||
import org.jetbrains.jps.incremental.storage.StorageOwner
|
||||
import org.jetbrains.kotlin.incremental.LookupStorage
|
||||
import java.io.File
|
||||
|
||||
internal class FileToIdMap(file: File) : BasicMap<File, Int>(file, FileKeyDescriptor, IntExternalizer) {
|
||||
override fun dumpKey(key: File): String = key.toString()
|
||||
|
||||
override fun dumpValue(value: Int): String = value.toString()
|
||||
|
||||
operator fun get(file: File): Int? = storage[file]
|
||||
|
||||
operator fun set(file: File, id: Int) {
|
||||
storage[file] = id
|
||||
}
|
||||
|
||||
fun remove(file: File) {
|
||||
storage.remove(file)
|
||||
}
|
||||
|
||||
fun toMap(): Map<File, Int> = storage.keys.keysToMap { storage[it]!! }
|
||||
object JpsLookupStorageProvider : StorageProvider<JpsLookupStorage>() {
|
||||
override fun createStorage(targetDataDir: File): JpsLookupStorage = JpsLookupStorage(targetDataDir)
|
||||
}
|
||||
|
||||
class JpsLookupStorage(targetDataDir: File) : StorageOwner, LookupStorage(targetDataDir)
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 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.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(
|
||||
private val file: File,
|
||||
private val fileContents: ByteArray,
|
||||
className: ClassId,
|
||||
classHeader: KotlinClassHeader,
|
||||
innerClasses: FileBasedKotlinClass.InnerClassesInfo
|
||||
) : FileBasedKotlinClass(className, classHeader, innerClasses) {
|
||||
|
||||
companion object {
|
||||
fun create(file: File): LocalFileKotlinClass? {
|
||||
val fileContents = file.readBytes()
|
||||
return FileBasedKotlinClass.create(fileContents) {
|
||||
className, classHeader, innerClasses ->
|
||||
LocalFileKotlinClass(file, fileContents, className, classHeader, innerClasses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val className: JvmClassName by lazy { JvmClassName.byClassId(classId) }
|
||||
|
||||
override fun getLocation(): String = file.absolutePath
|
||||
|
||||
public override fun getFileContents(): ByteArray = fileContents
|
||||
|
||||
override fun hashCode(): Int = file.hashCode()
|
||||
override fun equals(other: Any?): Boolean = other is LocalFileKotlinClass && file == other.file
|
||||
override fun toString(): String = "$javaClass: $file"
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental
|
||||
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.jps.builders.storage.StorageProvider
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.incremental.components.Position
|
||||
import org.jetbrains.kotlin.incremental.components.ScopeKind
|
||||
import org.jetbrains.kotlin.jps.incremental.storage.*
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
object LookupStorageProvider : StorageProvider<LookupStorage>() {
|
||||
override fun createStorage(targetDataDir: File): LookupStorage = LookupStorage(targetDataDir)
|
||||
}
|
||||
|
||||
class LookupStorage(private val targetDataDir: File) : BasicMapsOwner() {
|
||||
companion object {
|
||||
private val DELETED_TO_SIZE_TRESHOLD = 0.5
|
||||
private val MINIMUM_GARBAGE_COLLECTIBLE_SIZE = 10000
|
||||
}
|
||||
|
||||
private val String.storageFile: File
|
||||
get() = File(targetDataDir, this + "." + CACHE_EXTENSION)
|
||||
|
||||
private val countersFile = "counters".storageFile
|
||||
private val idToFile = registerMap(IdToFileMap("id-to-file".storageFile))
|
||||
private val fileToId = registerMap(FileToIdMap("file-to-id".storageFile))
|
||||
private val lookupMap = registerMap(LookupMap("lookups".storageFile))
|
||||
private var size: Int = 0
|
||||
private var deletedCount: Int = 0
|
||||
|
||||
init {
|
||||
if (countersFile.exists()) {
|
||||
val lines = countersFile.readLines()
|
||||
size = lines[0].toInt()
|
||||
deletedCount = lines[1].toInt()
|
||||
}
|
||||
}
|
||||
|
||||
fun get(lookupSymbol: LookupSymbol): Collection<String> {
|
||||
val key = LookupSymbolKey(lookupSymbol.name, lookupSymbol.scope)
|
||||
val fileIds = lookupMap[key] ?: return emptySet()
|
||||
|
||||
return fileIds.mapNotNull {
|
||||
// null means it's outdated
|
||||
idToFile[it]?.path
|
||||
}
|
||||
}
|
||||
|
||||
fun add(lookupSymbol: LookupSymbol, containingPaths: Collection<String>) {
|
||||
val key = LookupSymbolKey(lookupSymbol.name, lookupSymbol.scope)
|
||||
val fileIds = containingPaths.map { addFileIfNeeded(File(it)) }.toHashSet()
|
||||
fileIds.addAll(lookupMap[key] ?: emptySet())
|
||||
lookupMap[key] = fileIds
|
||||
}
|
||||
|
||||
fun removeLookupsFrom(file: File) {
|
||||
val id = fileToId[file] ?: return
|
||||
idToFile.remove(id)
|
||||
fileToId.remove(file)
|
||||
deletedCount++
|
||||
}
|
||||
|
||||
override fun clean() {
|
||||
if (countersFile.exists()) {
|
||||
countersFile.delete()
|
||||
}
|
||||
|
||||
size = 0
|
||||
deletedCount = 0
|
||||
|
||||
super.clean()
|
||||
}
|
||||
|
||||
override fun flush(memoryCachesOnly: Boolean) {
|
||||
try {
|
||||
removeGarbageIfNeeded()
|
||||
|
||||
if (size > 0) {
|
||||
if (!countersFile.exists()) {
|
||||
countersFile.parentFile.mkdirs()
|
||||
countersFile.createNewFile()
|
||||
}
|
||||
|
||||
countersFile.writeText("$size\n$deletedCount")
|
||||
}
|
||||
}
|
||||
finally {
|
||||
super.flush(memoryCachesOnly)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addFileIfNeeded(file: File): Int {
|
||||
val existing = fileToId[file]
|
||||
if (existing != null) return existing
|
||||
|
||||
val id = size++
|
||||
fileToId[file] = id
|
||||
idToFile[id] = file
|
||||
return id
|
||||
}
|
||||
|
||||
private fun removeGarbageIfNeeded(force: Boolean = false) {
|
||||
if (!force && size <= MINIMUM_GARBAGE_COLLECTIBLE_SIZE && deletedCount.toDouble() / size <= DELETED_TO_SIZE_TRESHOLD) return
|
||||
|
||||
for (hash in lookupMap.keys) {
|
||||
lookupMap[hash] = lookupMap[hash]!!.filter { it in idToFile }.toSet()
|
||||
}
|
||||
|
||||
val oldFileToId = fileToId.toMap()
|
||||
val oldIdToNewId = HashMap<Int, Int>(oldFileToId.size)
|
||||
idToFile.clean()
|
||||
fileToId.clean()
|
||||
size = 0
|
||||
deletedCount = 0
|
||||
|
||||
for ((file, oldId) in oldFileToId.entries) {
|
||||
val newId = addFileIfNeeded(file)
|
||||
oldIdToNewId[oldId] = newId
|
||||
}
|
||||
|
||||
for (lookup in lookupMap.keys) {
|
||||
val fileIds = lookupMap[lookup]!!.mapNotNull { oldIdToNewId[it] }.toSet()
|
||||
|
||||
if (fileIds.isEmpty()) {
|
||||
lookupMap.remove(lookup)
|
||||
}
|
||||
else {
|
||||
lookupMap[lookup] = fileIds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly fun forceGC() {
|
||||
removeGarbageIfNeeded(force = true)
|
||||
flush(false)
|
||||
}
|
||||
|
||||
@TestOnly fun dump(lookupSymbols: Set<LookupSymbol>): String {
|
||||
flush(false)
|
||||
|
||||
val sb = StringBuilder()
|
||||
val p = Printer(sb)
|
||||
val lookupsStrings = lookupSymbols.groupBy { LookupSymbolKey(it.name, it.scope) }
|
||||
|
||||
for (lookup in lookupMap.keys.sorted()) {
|
||||
val fileIds = lookupMap[lookup]!!
|
||||
|
||||
val key = if (lookup in lookupsStrings) {
|
||||
lookupsStrings[lookup]!!.map { "${it.scope}#${it.name}" }.sorted().joinToString(", ")
|
||||
}
|
||||
else {
|
||||
lookup.toString()
|
||||
}
|
||||
|
||||
val value = fileIds.map { idToFile[it]?.absolutePath ?: it.toString() }.sorted().joinToString(", ")
|
||||
p.println("$key -> $value")
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
|
||||
class LookupTrackerImpl(private val delegate: LookupTracker) : LookupTracker {
|
||||
val lookups = MultiMap<LookupSymbol, String>()
|
||||
|
||||
override val requiresPosition: Boolean
|
||||
get() = delegate.requiresPosition
|
||||
|
||||
override fun record(filePath: String, position: Position, scopeFqName: String, scopeKind: ScopeKind, name: String) {
|
||||
lookups.putValue(LookupSymbol(name, scopeFqName), filePath)
|
||||
delegate.record(filePath, position, scopeFqName, scopeKind, name)
|
||||
}
|
||||
}
|
||||
|
||||
data class LookupSymbol(val name: String, val scope: String)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,277 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental
|
||||
|
||||
import com.google.protobuf.MessageLite
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.jps.incremental.ProtoCompareGenerated.ProtoBufClassKind
|
||||
import org.jetbrains.kotlin.jps.incremental.ProtoCompareGenerated.ProtoBufPackageKind
|
||||
import org.jetbrains.kotlin.jps.incremental.storage.ProtoMapValue
|
||||
import org.jetbrains.kotlin.serialization.Flags
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.deserialization.Deserialization
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
|
||||
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
|
||||
import org.jetbrains.kotlin.utils.HashSetUtil
|
||||
import java.util.*
|
||||
|
||||
data class Difference(
|
||||
val isClassAffected: Boolean = false,
|
||||
val areSubclassesAffected: Boolean = false,
|
||||
val changedMembersNames: Set<String> = emptySet()
|
||||
)
|
||||
|
||||
fun difference(oldData: ProtoMapValue, newData: ProtoMapValue): Difference {
|
||||
if (!oldData.isPackageFacade && newData.isPackageFacade) return Difference(isClassAffected = true, areSubclassesAffected = true)
|
||||
|
||||
if (oldData.isPackageFacade && !newData.isPackageFacade) return Difference(isClassAffected = true)
|
||||
|
||||
val differenceObject =
|
||||
if (oldData.isPackageFacade) {
|
||||
DifferenceCalculatorForPackageFacade(oldData, newData)
|
||||
}
|
||||
else {
|
||||
DifferenceCalculatorForClass(oldData, newData)
|
||||
}
|
||||
|
||||
return differenceObject.difference()
|
||||
}
|
||||
|
||||
internal val MessageLite.isPrivate: Boolean
|
||||
get() = Visibilities.isPrivate(Deserialization.visibility(
|
||||
when (this) {
|
||||
is ProtoBuf.Constructor -> Flags.VISIBILITY.get(flags)
|
||||
is ProtoBuf.Function -> Flags.VISIBILITY.get(flags)
|
||||
is ProtoBuf.Property -> Flags.VISIBILITY.get(flags)
|
||||
else -> error("Unknown message: $this")
|
||||
}))
|
||||
|
||||
private fun MessageLite.name(nameResolver: NameResolver): String {
|
||||
return when (this) {
|
||||
is ProtoBuf.Constructor -> "<init>"
|
||||
is ProtoBuf.Function -> nameResolver.getString(name)
|
||||
is ProtoBuf.Property -> nameResolver.getString(name)
|
||||
else -> error("Unknown message: $this")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<MessageLite>.names(nameResolver: NameResolver): List<String> = map { it.name(nameResolver) }
|
||||
|
||||
private abstract class DifferenceCalculator() {
|
||||
protected abstract val oldNameResolver: NameResolver
|
||||
protected abstract val newNameResolver: NameResolver
|
||||
|
||||
protected val compareObject by lazy { ProtoCompareGenerated(oldNameResolver, newNameResolver) }
|
||||
|
||||
abstract fun difference(): Difference
|
||||
|
||||
protected fun calcDifferenceForMembers(oldList: List<MessageLite>, newList: List<MessageLite>): Collection<String> {
|
||||
val result = hashSetOf<String>()
|
||||
|
||||
val oldMap =
|
||||
oldList.groupBy { it.getHashCode({ compareObject.oldGetIndexOfString(it) }, { compareObject.oldGetIndexOfClassId(it) }) }
|
||||
val newMap =
|
||||
newList.groupBy { it.getHashCode({ compareObject.newGetIndexOfString(it) }, { compareObject.newGetIndexOfClassId(it) }) }
|
||||
|
||||
val hashes = oldMap.keys + newMap.keys
|
||||
for (hash in hashes) {
|
||||
val oldMembers = oldMap[hash]
|
||||
val newMembers = newMap[hash]
|
||||
|
||||
val differentMembers = when {
|
||||
newMembers == null -> oldMembers!!.names(compareObject.oldNameResolver)
|
||||
oldMembers == null -> newMembers.names(compareObject.newNameResolver)
|
||||
else -> calcDifferenceForEqualHashes(oldMembers, newMembers)
|
||||
}
|
||||
result.addAll(differentMembers)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun calcDifferenceForEqualHashes(
|
||||
oldList: List<MessageLite>,
|
||||
newList: List<MessageLite>
|
||||
): Collection<String> {
|
||||
val result = hashSetOf<String>()
|
||||
val newSet = HashSet(newList)
|
||||
|
||||
oldList.forEach { oldMember ->
|
||||
val newMember = newSet.firstOrNull { compareObject.checkEquals(oldMember, it) }
|
||||
if (newMember != null) {
|
||||
newSet.remove(newMember)
|
||||
}
|
||||
else {
|
||||
result.add(oldMember.name(compareObject.oldNameResolver))
|
||||
}
|
||||
}
|
||||
|
||||
newSet.forEach { newMember ->
|
||||
result.add(newMember.name(compareObject.newNameResolver))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
protected fun calcDifferenceForNames(
|
||||
oldList: List<Int>,
|
||||
newList: List<Int>
|
||||
): Collection<String> {
|
||||
val oldNames = oldList.map { compareObject.oldNameResolver.getString(it) }.toSet()
|
||||
val newNames = newList.map { compareObject.newNameResolver.getString(it) }.toSet()
|
||||
return HashSetUtil.symmetricDifference(oldNames, newNames)
|
||||
}
|
||||
|
||||
private fun MessageLite.getHashCode(stringIndexes: (Int) -> Int, fqNameIndexes: (Int) -> Int): Int {
|
||||
return when (this) {
|
||||
is ProtoBuf.Constructor -> hashCode(stringIndexes, fqNameIndexes)
|
||||
is ProtoBuf.Function -> hashCode(stringIndexes, fqNameIndexes)
|
||||
is ProtoBuf.Property -> hashCode(stringIndexes, fqNameIndexes)
|
||||
else -> error("Unknown message: $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ProtoCompareGenerated.checkEquals(old: MessageLite, new: MessageLite): Boolean {
|
||||
return when {
|
||||
old is ProtoBuf.Constructor && new is ProtoBuf.Constructor -> checkEquals(old, new)
|
||||
old is ProtoBuf.Function && new is ProtoBuf.Function -> checkEquals(old, new)
|
||||
old is ProtoBuf.Property && new is ProtoBuf.Property -> checkEquals(old, new)
|
||||
else -> error("Unknown message: $this")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DifferenceCalculatorForClass(oldData: ProtoMapValue, newData: ProtoMapValue) : DifferenceCalculator() {
|
||||
companion object {
|
||||
private val CLASS_SIGNATURE_ENUMS = EnumSet.of(
|
||||
ProtoBufClassKind.FLAGS,
|
||||
ProtoBufClassKind.FQ_NAME,
|
||||
ProtoBufClassKind.TYPE_PARAMETER_LIST,
|
||||
ProtoBufClassKind.SUPERTYPE_LIST
|
||||
)
|
||||
}
|
||||
|
||||
val oldClassData = JvmProtoBufUtil.readClassDataFrom(oldData.bytes, oldData.strings)
|
||||
val newClassData = JvmProtoBufUtil.readClassDataFrom(newData.bytes, newData.strings)
|
||||
|
||||
val oldProto = oldClassData.classProto
|
||||
val newProto = newClassData.classProto
|
||||
|
||||
override val oldNameResolver = oldClassData.nameResolver
|
||||
override val newNameResolver = newClassData.nameResolver
|
||||
|
||||
val diff = compareObject.difference(oldProto, newProto)
|
||||
|
||||
override fun difference(): Difference {
|
||||
var isClassAffected = false
|
||||
var areSubclassesAffected = false
|
||||
val names = hashSetOf<String>()
|
||||
val classIsSealed = newProto.isSealed && oldProto.isSealed
|
||||
|
||||
fun Int.oldToNames() = names.add(oldNameResolver.getString(this))
|
||||
fun Int.newToNames() = names.add(newNameResolver.getString(this))
|
||||
|
||||
fun calcDifferenceForNonPrivateMembers(members: (ProtoBuf.Class) -> List<MessageLite>): Collection<String> {
|
||||
val oldMembers = members(oldProto).filterNot { it.isPrivate }
|
||||
val newMembers = members(newProto).filterNot { it.isPrivate }
|
||||
return calcDifferenceForMembers(oldMembers, newMembers)
|
||||
}
|
||||
|
||||
for (kind in diff) {
|
||||
when (kind!!) {
|
||||
ProtoBufClassKind.COMPANION_OBJECT_NAME -> {
|
||||
if (oldProto.hasCompanionObjectName()) oldProto.companionObjectName.oldToNames()
|
||||
if (newProto.hasCompanionObjectName()) newProto.companionObjectName.newToNames()
|
||||
}
|
||||
ProtoBufClassKind.NESTED_CLASS_NAME_LIST -> {
|
||||
if (classIsSealed) {
|
||||
// when class is sealed, adding an implementation can break exhaustive when expressions
|
||||
// the workaround is to recompile all class usages
|
||||
isClassAffected = true
|
||||
}
|
||||
|
||||
names.addAll(calcDifferenceForNames(oldProto.nestedClassNameList, newProto.nestedClassNameList))
|
||||
}
|
||||
ProtoBufClassKind.CONSTRUCTOR_LIST -> {
|
||||
val differentNonPrivateConstructors = calcDifferenceForNonPrivateMembers(ProtoBuf.Class::getConstructorList)
|
||||
|
||||
if (differentNonPrivateConstructors.isNotEmpty()) {
|
||||
isClassAffected = true
|
||||
}
|
||||
}
|
||||
ProtoBufClassKind.FUNCTION_LIST ->
|
||||
names.addAll(calcDifferenceForNonPrivateMembers(ProtoBuf.Class::getFunctionList))
|
||||
ProtoBufClassKind.PROPERTY_LIST ->
|
||||
names.addAll(calcDifferenceForNonPrivateMembers(ProtoBuf.Class::getPropertyList))
|
||||
ProtoBufClassKind.ENUM_ENTRY_LIST -> {
|
||||
isClassAffected = true
|
||||
}
|
||||
ProtoBufClassKind.TYPE_TABLE -> {
|
||||
// TODO
|
||||
}
|
||||
in CLASS_SIGNATURE_ENUMS -> {
|
||||
isClassAffected = true
|
||||
areSubclassesAffected = true
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unsupported kind: $kind")
|
||||
}
|
||||
}
|
||||
|
||||
return Difference(isClassAffected, areSubclassesAffected, names)
|
||||
}
|
||||
}
|
||||
|
||||
private class DifferenceCalculatorForPackageFacade(oldData: ProtoMapValue, newData: ProtoMapValue) : DifferenceCalculator() {
|
||||
val oldPackageData = JvmProtoBufUtil.readPackageDataFrom(oldData.bytes, oldData.strings)
|
||||
val newPackageData = JvmProtoBufUtil.readPackageDataFrom(newData.bytes, newData.strings)
|
||||
|
||||
val oldProto = oldPackageData.packageProto
|
||||
val newProto = newPackageData.packageProto
|
||||
|
||||
override val oldNameResolver = oldPackageData.nameResolver
|
||||
override val newNameResolver = newPackageData.nameResolver
|
||||
|
||||
val diff = compareObject.difference(oldProto, newProto)
|
||||
|
||||
override fun difference(): Difference {
|
||||
val names = hashSetOf<String>()
|
||||
|
||||
fun calcDifferenceForNonPrivateMembers(members: (ProtoBuf.Package) -> List<MessageLite>): Collection<String> {
|
||||
val oldMembers = members(oldProto).filterNot { it.isPrivate }
|
||||
val newMembers = members(newProto).filterNot { it.isPrivate }
|
||||
return calcDifferenceForMembers(oldMembers, newMembers)
|
||||
}
|
||||
|
||||
for (kind in diff) {
|
||||
when (kind!!) {
|
||||
ProtoBufPackageKind.FUNCTION_LIST ->
|
||||
names.addAll(calcDifferenceForNonPrivateMembers(ProtoBuf.Package::getFunctionList))
|
||||
ProtoBufPackageKind.PROPERTY_LIST ->
|
||||
names.addAll(calcDifferenceForNonPrivateMembers(ProtoBuf.Package::getPropertyList))
|
||||
ProtoBufPackageKind.TYPE_TABLE -> {
|
||||
// TODO
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unsupported kind: $kind")
|
||||
}
|
||||
}
|
||||
|
||||
return Difference(changedMembersNames = names)
|
||||
}
|
||||
}
|
||||
|
||||
private val ProtoBuf.Class.isSealed: Boolean
|
||||
get() = ProtoBuf.Modality.SEALED == Flags.MODALITY.get(flags)
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental.storage
|
||||
|
||||
import com.intellij.util.io.DataExternalizer
|
||||
import com.intellij.util.io.EnumeratorStringDescriptor
|
||||
import com.intellij.util.io.KeyDescriptor
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
|
||||
internal abstract class BasicMap<K : Comparable<K>, V>(
|
||||
storageFile: File,
|
||||
keyDescriptor: KeyDescriptor<K>,
|
||||
valueExternalizer: DataExternalizer<V>
|
||||
) {
|
||||
protected val storage = LazyStorage(storageFile, keyDescriptor, valueExternalizer)
|
||||
|
||||
open fun clean() {
|
||||
storage.clean()
|
||||
}
|
||||
|
||||
fun flush(memoryCachesOnly: Boolean) {
|
||||
storage.flush(memoryCachesOnly)
|
||||
}
|
||||
|
||||
fun close() {
|
||||
storage.close()
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun dump(): String {
|
||||
return with(StringBuilder()) {
|
||||
with(Printer(this)) {
|
||||
println(this@BasicMap.javaClass.simpleName)
|
||||
pushIndent()
|
||||
|
||||
for (key in storage.keys.sorted()) {
|
||||
println("${dumpKey(key)} -> ${dumpValue(storage[key]!!)}")
|
||||
}
|
||||
|
||||
popIndent()
|
||||
}
|
||||
|
||||
this
|
||||
}.toString()
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
protected abstract fun dumpKey(key: K): String
|
||||
|
||||
@TestOnly
|
||||
protected abstract fun dumpValue(value: V): String
|
||||
}
|
||||
|
||||
internal abstract class BasicStringMap<V>(
|
||||
storageFile: File,
|
||||
keyDescriptor: KeyDescriptor<String>,
|
||||
valueExternalizer: DataExternalizer<V>
|
||||
) : BasicMap<String, V>(storageFile, keyDescriptor, valueExternalizer) {
|
||||
constructor(
|
||||
storageFile: File,
|
||||
valueExternalizer: DataExternalizer<V>
|
||||
) : this(storageFile, EnumeratorStringDescriptor.INSTANCE, valueExternalizer)
|
||||
|
||||
override fun dumpKey(key: String): String = key
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental.storage
|
||||
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.jps.incremental.storage.StorageOwner
|
||||
|
||||
open class BasicMapsOwner : StorageOwner {
|
||||
private val maps = arrayListOf<BasicMap<*, *>>()
|
||||
|
||||
companion object {
|
||||
val CACHE_EXTENSION = "tab"
|
||||
}
|
||||
|
||||
protected fun <K, V, M : BasicMap<K, V>> registerMap(map: M): M {
|
||||
maps.add(map)
|
||||
return map
|
||||
}
|
||||
|
||||
override fun clean() {
|
||||
maps.forEach { it.clean() }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
maps.forEach { it.close() }
|
||||
}
|
||||
|
||||
override fun flush(memoryCachesOnly: Boolean) {
|
||||
maps.forEach { it.flush(memoryCachesOnly) }
|
||||
}
|
||||
|
||||
@TestOnly fun dump(): String = maps.map { it.dump() }.joinToString("\n\n")
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental.storage
|
||||
|
||||
import org.jetbrains.kotlin.jps.incremental.dumpCollection
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import java.io.File
|
||||
|
||||
internal open class ClassOneToManyMap(
|
||||
storageFile: File
|
||||
) : BasicStringMap<Collection<String>>(storageFile, StringCollectionExternalizer) {
|
||||
override fun dumpValue(value: Collection<String>): String = value.dumpCollection()
|
||||
|
||||
fun add(key: FqName, value: FqName) {
|
||||
storage.append(key.asString(), value.asString())
|
||||
}
|
||||
|
||||
operator fun get(key: FqName): Collection<FqName> =
|
||||
storage[key.asString()]?.map(::FqName) ?: setOf()
|
||||
|
||||
operator fun set(key: FqName, values: Collection<FqName>) {
|
||||
if (values.isEmpty()) {
|
||||
remove(key)
|
||||
return
|
||||
}
|
||||
|
||||
storage[key.asString()] = values.map(FqName::asString)
|
||||
}
|
||||
|
||||
fun remove(key: FqName) {
|
||||
storage.remove(key.asString())
|
||||
}
|
||||
|
||||
fun removeValues(key: FqName, removed: Set<FqName>) {
|
||||
val notRemoved = this[key].filter { it !in removed }
|
||||
this[key] = notRemoved
|
||||
}
|
||||
}
|
||||
|
||||
internal class SubtypesMap(storageFile: File) : ClassOneToManyMap(storageFile)
|
||||
internal class SupertypesMap(storageFile: File) : ClassOneToManyMap(storageFile)
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental.storage
|
||||
|
||||
import com.intellij.util.io.ExternalIntegerKeyDescriptor
|
||||
import java.io.File
|
||||
|
||||
internal class IdToFileMap(file: File) : BasicMap<Int, File>(file, ExternalIntegerKeyDescriptor(), FileKeyDescriptor) {
|
||||
override fun dumpKey(key: Int): String = key.toString()
|
||||
|
||||
override fun dumpValue(value: File): String = value.toString()
|
||||
|
||||
operator fun get(id: Int): File? = storage[id]
|
||||
|
||||
operator fun contains(id: Int): Boolean = id in storage
|
||||
|
||||
operator fun set(id: Int, file: File) {
|
||||
storage[id] = file
|
||||
}
|
||||
|
||||
fun remove(id: Int) {
|
||||
storage.remove(id)
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental.storage
|
||||
|
||||
import com.intellij.util.io.DataExternalizer
|
||||
import com.intellij.util.io.IOUtil
|
||||
import com.intellij.util.io.KeyDescriptor
|
||||
import com.intellij.util.io.PersistentHashMap
|
||||
import java.io.DataOutput
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
/**
|
||||
* It's lazy in a sense that PersistentHashMap is created only on write
|
||||
*/
|
||||
internal class LazyStorage<K, V>(
|
||||
private val storageFile: File,
|
||||
private val keyDescriptor: KeyDescriptor<K>,
|
||||
private val valueExternalizer: DataExternalizer<V>
|
||||
) {
|
||||
@Volatile
|
||||
private var storage: PersistentHashMap<K, V>? = null
|
||||
|
||||
@Synchronized
|
||||
private fun getStorageIfExists(): PersistentHashMap<K, V>? {
|
||||
if (storage != null) return storage
|
||||
|
||||
if (storageFile.exists()) {
|
||||
storage = createMap()
|
||||
return storage
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun getStorageOrCreateNew(): PersistentHashMap<K, V> {
|
||||
if (storage == null) {
|
||||
storage = createMap()
|
||||
}
|
||||
|
||||
return storage!!
|
||||
}
|
||||
|
||||
val keys: Collection<K>
|
||||
get() = getStorageIfExists()?.allKeysWithExistingMapping ?: listOf()
|
||||
|
||||
operator fun contains(key: K): Boolean =
|
||||
getStorageIfExists()?.containsMapping(key) ?: false
|
||||
|
||||
operator fun get(key: K): V? =
|
||||
getStorageIfExists()?.get(key)
|
||||
|
||||
operator fun set(key: K, value: V) {
|
||||
getStorageOrCreateNew().put(key, value)
|
||||
}
|
||||
|
||||
fun remove(key: K) {
|
||||
getStorageIfExists()?.remove(key)
|
||||
}
|
||||
|
||||
fun append(key: K, value: String) {
|
||||
append(key) { out -> IOUtil.writeUTF(out, value) }
|
||||
}
|
||||
|
||||
fun append(key: K, value: Int) {
|
||||
append(key) { out -> out.writeInt(value) }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun clean() {
|
||||
try {
|
||||
storage?.close()
|
||||
}
|
||||
catch (ignored: IOException) {
|
||||
}
|
||||
|
||||
PersistentHashMap.deleteFilesStartingWith(storageFile)
|
||||
storage = null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun flush(memoryCachesOnly: Boolean) {
|
||||
val existingStorage = storage ?: return
|
||||
|
||||
if (memoryCachesOnly) {
|
||||
if (existingStorage.isDirty) {
|
||||
existingStorage.dropMemoryCaches()
|
||||
}
|
||||
}
|
||||
else {
|
||||
existingStorage.force()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun close() {
|
||||
storage?.close()
|
||||
}
|
||||
|
||||
private fun createMap(): PersistentHashMap<K, V> =
|
||||
PersistentHashMap(storageFile, keyDescriptor, valueExternalizer)
|
||||
|
||||
private fun append(key: K, append: (DataOutput)->Unit) {
|
||||
getStorageOrCreateNew().appendData(key, append)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental.storage
|
||||
|
||||
import java.io.File
|
||||
|
||||
internal class LookupMap(storage: File) : BasicMap<LookupSymbolKey, Collection<Int>>(storage, LookupSymbolKeyDescriptor, IntCollectionExternalizer) {
|
||||
override fun dumpKey(key: LookupSymbolKey): String = key.toString()
|
||||
|
||||
override fun dumpValue(value: Collection<Int>): String = value.toString()
|
||||
|
||||
fun add(name: String, scope: String, fileId: Int) {
|
||||
storage.append(LookupSymbolKey(name, scope), fileId)
|
||||
}
|
||||
|
||||
operator fun get(key: LookupSymbolKey): Collection<Int>? = storage[key]
|
||||
|
||||
operator fun set(key: LookupSymbolKey, fileIds: Set<Int>) {
|
||||
storage[key] = fileIds
|
||||
}
|
||||
|
||||
fun remove(key: LookupSymbolKey) {
|
||||
storage.remove(key)
|
||||
}
|
||||
|
||||
val keys: Collection<LookupSymbolKey>
|
||||
get() = storage.keys
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental.storage
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.io.DataExternalizer
|
||||
import com.intellij.util.io.EnumeratorStringDescriptor
|
||||
import com.intellij.util.io.IOUtil
|
||||
import com.intellij.util.io.KeyDescriptor
|
||||
import gnu.trove.THashSet
|
||||
import org.jetbrains.jps.incremental.storage.PathStringDescriptor
|
||||
import java.io.DataInput
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutput
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
object LookupSymbolKeyDescriptor : KeyDescriptor<LookupSymbolKey> {
|
||||
override fun read(input: DataInput): LookupSymbolKey {
|
||||
val first = input.readInt()
|
||||
val second = input.readInt()
|
||||
|
||||
return LookupSymbolKey(first, second)
|
||||
}
|
||||
|
||||
override fun save(output: DataOutput, value: LookupSymbolKey) {
|
||||
output.writeInt(value.nameHash)
|
||||
output.writeInt(value.scopeHash)
|
||||
}
|
||||
|
||||
override fun getHashCode(value: LookupSymbolKey): Int = value.hashCode()
|
||||
|
||||
override fun isEqual(val1: LookupSymbolKey, val2: LookupSymbolKey): Boolean = val1 == val2
|
||||
}
|
||||
|
||||
|
||||
object PathFunctionPairKeyDescriptor : KeyDescriptor<PathFunctionPair> {
|
||||
override fun read(input: DataInput): PathFunctionPair {
|
||||
val path = IOUtil.readUTF(input)
|
||||
val function = IOUtil.readUTF(input)
|
||||
return PathFunctionPair(path, function)
|
||||
}
|
||||
|
||||
override fun save(output: DataOutput, value: PathFunctionPair) {
|
||||
IOUtil.writeUTF(output, value.path)
|
||||
IOUtil.writeUTF(output, value.function)
|
||||
}
|
||||
|
||||
override fun getHashCode(value: PathFunctionPair): Int = value.hashCode()
|
||||
|
||||
override fun isEqual(val1: PathFunctionPair, val2: PathFunctionPair): Boolean = val1 == val2
|
||||
}
|
||||
|
||||
|
||||
object ProtoMapValueExternalizer : DataExternalizer<ProtoMapValue> {
|
||||
override fun save(output: DataOutput, value: ProtoMapValue) {
|
||||
output.writeBoolean(value.isPackageFacade)
|
||||
output.writeInt(value.bytes.size)
|
||||
output.write(value.bytes)
|
||||
output.writeInt(value.strings.size)
|
||||
|
||||
for (string in value.strings) {
|
||||
output.writeUTF(string)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): ProtoMapValue {
|
||||
val isPackageFacade = input.readBoolean()
|
||||
val bytesLength = input.readInt()
|
||||
val bytes = ByteArray(bytesLength)
|
||||
input.readFully(bytes, 0, bytesLength)
|
||||
val stringsLength = input.readInt()
|
||||
val strings = Array<String>(stringsLength) { input.readUTF() }
|
||||
return ProtoMapValue(isPackageFacade, bytes, strings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class StringMapExternalizer<T> : DataExternalizer<Map<String, T>> {
|
||||
override fun save(output: DataOutput, map: Map<String, T>?) {
|
||||
output.writeInt(map!!.size)
|
||||
|
||||
for ((key, value) in map.entries) {
|
||||
IOUtil.writeString(key, output)
|
||||
writeValue(output, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): Map<String, T>? {
|
||||
val size = input.readInt()
|
||||
val map = HashMap<String, T>(size)
|
||||
|
||||
repeat(size) {
|
||||
val name = IOUtil.readString(input)!!
|
||||
map[name] = readValue(input)
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
protected abstract fun writeValue(output: DataOutput, value: T)
|
||||
protected abstract fun readValue(input: DataInput): T
|
||||
}
|
||||
|
||||
|
||||
object StringToLongMapExternalizer : StringMapExternalizer<Long>() {
|
||||
override fun readValue(input: DataInput): Long = input.readLong()
|
||||
|
||||
override fun writeValue(output: DataOutput, value: Long) {
|
||||
output.writeLong(value)
|
||||
}
|
||||
}
|
||||
|
||||
object ConstantsMapExternalizer : DataExternalizer<Map<String, Any>> {
|
||||
override fun save(output: DataOutput, map: Map<String, Any>?) {
|
||||
output.writeInt(map!!.size)
|
||||
for (name in map.keys.sorted()) {
|
||||
IOUtil.writeString(name, output)
|
||||
val value = map[name]!!
|
||||
when (value) {
|
||||
is Int -> {
|
||||
output.writeByte(Kind.INT.ordinal)
|
||||
output.writeInt(value)
|
||||
}
|
||||
is Float -> {
|
||||
output.writeByte(Kind.FLOAT.ordinal)
|
||||
output.writeFloat(value)
|
||||
}
|
||||
is Long -> {
|
||||
output.writeByte(Kind.LONG.ordinal)
|
||||
output.writeLong(value)
|
||||
}
|
||||
is Double -> {
|
||||
output.writeByte(Kind.DOUBLE.ordinal)
|
||||
output.writeDouble(value)
|
||||
}
|
||||
is String -> {
|
||||
output.writeByte(Kind.STRING.ordinal)
|
||||
IOUtil.writeString(value, output)
|
||||
}
|
||||
else -> throw IllegalStateException("Unexpected constant class: ${value.javaClass}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): Map<String, Any>? {
|
||||
val size = input.readInt()
|
||||
val map = HashMap<String, Any>(size)
|
||||
|
||||
repeat(size) {
|
||||
val name = IOUtil.readString(input)!!
|
||||
val kind = Kind.values()[input.readByte().toInt()]
|
||||
|
||||
val value: Any = when (kind) {
|
||||
Kind.INT -> input.readInt()
|
||||
Kind.FLOAT -> input.readFloat()
|
||||
Kind.LONG -> input.readLong()
|
||||
Kind.DOUBLE -> input.readDouble()
|
||||
Kind.STRING -> IOUtil.readString(input)!!
|
||||
}
|
||||
|
||||
map[name] = value
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
private enum class Kind {
|
||||
INT, FLOAT, LONG, DOUBLE, STRING
|
||||
}
|
||||
}
|
||||
|
||||
object IntExternalizer : DataExternalizer<Int> {
|
||||
override fun read(input: DataInput): Int = input.readInt()
|
||||
|
||||
override fun save(output: DataOutput, value: Int) {
|
||||
output.writeInt(value)
|
||||
}
|
||||
}
|
||||
|
||||
object FileKeyDescriptor : KeyDescriptor<File> {
|
||||
override fun read(input: DataInput): File = File(input.readUTF())
|
||||
|
||||
override fun save(output: DataOutput, value: File) {
|
||||
output.writeUTF(value.canonicalPath)
|
||||
}
|
||||
|
||||
override fun getHashCode(value: File?): Int =
|
||||
FileUtil.FILE_HASHING_STRATEGY.computeHashCode(value)
|
||||
|
||||
override fun isEqual(val1: File?, val2: File?): Boolean =
|
||||
FileUtil.FILE_HASHING_STRATEGY.equals(val1, val2)
|
||||
}
|
||||
|
||||
open class CollectionExternalizer<T>(
|
||||
private val elementExternalizer: DataExternalizer<T>,
|
||||
private val newCollection: ()->MutableCollection<T>
|
||||
) : DataExternalizer<Collection<T>> {
|
||||
override fun read(input: DataInput): Collection<T> {
|
||||
val result = newCollection()
|
||||
val stream = input as DataInputStream
|
||||
|
||||
while (stream.available() > 0) {
|
||||
result.add(elementExternalizer.read(stream))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun save(output: DataOutput, value: Collection<T>) {
|
||||
value.forEach { elementExternalizer.save(output, it) }
|
||||
}
|
||||
}
|
||||
|
||||
object StringCollectionExternalizer : CollectionExternalizer<String>(EnumeratorStringDescriptor(), { HashSet() })
|
||||
|
||||
object PathCollectionExternalizer : CollectionExternalizer<String>(PathStringDescriptor(), { THashSet(FileUtil.PATH_HASHING_STRATEGY) })
|
||||
|
||||
object IntCollectionExternalizer : CollectionExternalizer<Int>(IntExternalizer, { HashSet() })
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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.jps.incremental.storage
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
|
||||
data class LookupSymbolKey(val nameHash: Int, val scopeHash: Int) : Comparable<LookupSymbolKey> {
|
||||
constructor(name: String, scope: String) : this(name.hashCode(), scope.hashCode())
|
||||
|
||||
override fun compareTo(other: LookupSymbolKey): Int {
|
||||
val nameCmp = nameHash.compareTo(other.nameHash)
|
||||
|
||||
if (nameCmp != 0) return nameCmp
|
||||
|
||||
return scopeHash.compareTo(other.scopeHash)
|
||||
}
|
||||
}
|
||||
|
||||
class PathFunctionPair(
|
||||
val path: String,
|
||||
val function: String
|
||||
): Comparable<PathFunctionPair> {
|
||||
override fun compareTo(other: PathFunctionPair): Int {
|
||||
val pathComp = FileUtil.comparePaths(path, other.path)
|
||||
|
||||
if (pathComp != 0) return pathComp
|
||||
|
||||
return function.compareTo(other.function)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
when (other) {
|
||||
is PathFunctionPair ->
|
||||
FileUtil.pathsEqual(path, other.path) && function == other.function
|
||||
else ->
|
||||
false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = 31 * FileUtil.pathHashCode(path) + function.hashCode()
|
||||
}
|
||||
|
||||
data class ProtoMapValue(val isPackageFacade: Boolean, val bytes: ByteArray, val strings: Array<String>)
|
||||
@@ -44,11 +44,11 @@ import org.jetbrains.jps.model.java.JpsJavaDependencyScope
|
||||
import org.jetbrains.jps.model.java.JpsJavaExtensionService
|
||||
import org.jetbrains.jps.util.JpsPathUtil
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.jps.build.classFilesComparison.assertEqualDirectories
|
||||
import org.jetbrains.kotlin.jps.incremental.JpsLookupStorageProvider
|
||||
import org.jetbrains.kotlin.jps.incremental.KotlinDataContainerTarget
|
||||
import org.jetbrains.kotlin.jps.incremental.LookupStorageProvider
|
||||
import org.jetbrains.kotlin.jps.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.jps.incremental.getKotlinCache
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
@@ -384,7 +384,7 @@ abstract class AbstractIncrementalJpsTest(
|
||||
p.println("Begin of Lookup Maps")
|
||||
p.println()
|
||||
|
||||
val lookupStorage = project.dataManager.getStorage(KotlinDataContainerTarget, LookupStorageProvider)
|
||||
val lookupStorage = project.dataManager.getStorage(KotlinDataContainerTarget, JpsLookupStorageProvider)
|
||||
lookupStorage.forceGC()
|
||||
p.print(lookupStorage.dump(lookupsDuringTest))
|
||||
|
||||
|
||||
+4
-4
@@ -20,11 +20,11 @@ import com.intellij.testFramework.UsefulTestCase
|
||||
import org.jetbrains.jps.builders.BuildTarget
|
||||
import org.jetbrains.jps.builders.storage.BuildDataPaths
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.jps.incremental.CacheVersion
|
||||
import org.jetbrains.kotlin.incremental.CacheVersion
|
||||
import org.jetbrains.kotlin.incremental.storage.BasicMapsOwner
|
||||
import org.jetbrains.kotlin.jps.incremental.CacheVersionProvider
|
||||
import org.jetbrains.kotlin.jps.incremental.KOTLIN_CACHE_DIRECTORY_NAME
|
||||
import org.jetbrains.kotlin.incremental.KOTLIN_CACHE_DIRECTORY_NAME
|
||||
import org.jetbrains.kotlin.jps.incremental.KotlinDataContainerTarget
|
||||
import org.jetbrains.kotlin.jps.incremental.storage.BasicMapsOwner
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
|
||||
@@ -119,4 +119,4 @@ abstract class AbstractIncrementalLazyCachesTest : AbstractIncrementalJpsTest()
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import com.google.common.hash.Hashing
|
||||
import com.google.common.io.Files
|
||||
import com.google.protobuf.ExtensionRegistry
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.jps.incremental.LocalFileKotlinClass
|
||||
import org.jetbrains.kotlin.incremental.LocalFileKotlinClass
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.serialization.DebugProtoBuf
|
||||
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
|
||||
|
||||
+4
-2
@@ -19,7 +19,9 @@ package org.jetbrains.kotlin.jps.incremental
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import com.intellij.util.SmartList
|
||||
import org.jetbrains.kotlin.jps.incremental.storage.ProtoMapValue
|
||||
import org.jetbrains.kotlin.incremental.LocalFileKotlinClass
|
||||
import org.jetbrains.kotlin.incremental.difference
|
||||
import org.jetbrains.kotlin.incremental.storage.ProtoMapValue
|
||||
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.serialization.jvm.BitEncoding
|
||||
@@ -124,4 +126,4 @@ abstract class AbstractProtoComparisonTest : UsefulTestCase() {
|
||||
FileUtil.createDirectory(directory)
|
||||
return directory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user