Rename module "build" -> "build-common"
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="frontend.java" />
|
||||
<orderEntry type="module" module-name="util.runtime" />
|
||||
<orderEntry type="module" module-name="cli-common" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
<orderEntry type="module" module-name="compiler-tests" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="idea-full" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.build
|
||||
|
||||
import java.io.File
|
||||
|
||||
data class JvmSourceRoot(val file: File, val packagePrefix: String? = null)
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.build
|
||||
|
||||
import org.jetbrains.kotlin.incremental.LocalFileKotlinClass
|
||||
import org.jetbrains.kotlin.load.kotlin.ModuleMapping
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
import java.io.File
|
||||
|
||||
open class GeneratedFile<Target>(
|
||||
val target: Target,
|
||||
val sourceFiles: Collection<File>,
|
||||
val outputFile: File
|
||||
)
|
||||
|
||||
class GeneratedJvmClass<Target> (
|
||||
target: Target,
|
||||
sourceFiles: Collection<File>,
|
||||
outputFile: File
|
||||
) : GeneratedFile<Target>(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"
|
||||
}
|
||||
}
|
||||
|
||||
fun File.isModuleMappingFile() = extension == ModuleMapping.MAPPING_FILE_EXT && parentFile.name == "META-INF"
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.compilerRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface OutputItemsCollector {
|
||||
void add(Collection<File> sourceFiles, File outputFile);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.compilerRunner;
|
||||
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class OutputItemsCollectorImpl implements OutputItemsCollector {
|
||||
private final List<SimpleOutputItem> outputs = ContainerUtil.newArrayList();
|
||||
|
||||
@Override
|
||||
public void add(Collection<File> sourceFiles, File outputFile) {
|
||||
outputs.add(new SimpleOutputItem(sourceFiles, outputFile));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<SimpleOutputItem> getOutputs() {
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.compilerRunner;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
public class SimpleOutputItem {
|
||||
private final Collection<File> sourceFiles;
|
||||
private final File outputFile;
|
||||
|
||||
public SimpleOutputItem(@NotNull Collection<File> sourceFiles, @NotNull File outputFile) {
|
||||
this.sourceFiles = sourceFiles;
|
||||
this.outputFile = outputFile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<File> getSourceFiles() {
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public File getOutputFile() {
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sourceFiles + " -> " + outputFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
fun normalCacheVersion(dataRoot: File): CacheVersion =
|
||||
CacheVersion(ownVersion = NORMAL_VERSION,
|
||||
versionFile = File(dataRoot, NORMAL_VERSION_FILE_NAME),
|
||||
whenVersionChanged = CacheVersion.Action.REBUILD_CHUNK,
|
||||
whenTurnedOn = CacheVersion.Action.REBUILD_CHUNK,
|
||||
whenTurnedOff = CacheVersion.Action.CLEAN_NORMAL_CACHES,
|
||||
isEnabled = { IncrementalCompilation.isEnabled() })
|
||||
|
||||
fun experimentalCacheVersion(dataRoot: File): CacheVersion =
|
||||
CacheVersion(ownVersion = EXPERIMENTAL_VERSION,
|
||||
versionFile = File(dataRoot, EXPERIMENTAL_VERSION_FILE_NAME),
|
||||
whenVersionChanged = CacheVersion.Action.REBUILD_CHUNK,
|
||||
whenTurnedOn = CacheVersion.Action.REBUILD_CHUNK,
|
||||
whenTurnedOff = CacheVersion.Action.CLEAN_EXPERIMENTAL_CACHES,
|
||||
isEnabled = { IncrementalCompilation.isExperimental() })
|
||||
|
||||
fun dataContainerCacheVersion(dataRoot: File): CacheVersion =
|
||||
CacheVersion(ownVersion = DATA_CONTAINER_VERSION,
|
||||
versionFile = File(dataRoot, DATA_CONTAINER_VERSION_FILE_NAME),
|
||||
whenVersionChanged = CacheVersion.Action.REBUILD_ALL_KOTLIN,
|
||||
whenTurnedOn = CacheVersion.Action.REBUILD_ALL_KOTLIN,
|
||||
whenTurnedOff = CacheVersion.Action.CLEAN_DATA_CONTAINER,
|
||||
isEnabled = { IncrementalCompilation.isExperimental() })
|
||||
|
||||
fun allCachesVersions(containerDataRoot: File, dataRoots: Iterable<File>): Iterable<CacheVersion> {
|
||||
val versions = arrayListOf<CacheVersion>()
|
||||
versions.add(dataContainerCacheVersion(containerDataRoot))
|
||||
|
||||
for (dataRoot in dataRoots) {
|
||||
versions.add(normalCacheVersion(dataRoot))
|
||||
versions.add(experimentalCacheVersion(dataRoot))
|
||||
}
|
||||
|
||||
return versions
|
||||
}
|
||||
@@ -0,0 +1,647 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
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.kotlin.build.GeneratedJvmClass
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.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<Target>(
|
||||
private val targetDataRoot: File,
|
||||
targetOutputDir: File?,
|
||||
target: Target
|
||||
) : 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(targetDataRoot, KOTLIN_CACHE_DIRECTORY_NAME)
|
||||
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<Target>>()
|
||||
private val outputDir by lazy(LazyThreadSafetyMode.NONE) { requireNotNull(targetOutputDir) { "Target is expected to have output directory: $target" } }
|
||||
|
||||
// TODO: review
|
||||
val dependentsWithThis: Sequence<IncrementalCacheImpl<Target>>
|
||||
get() = sequenceOf(this).plus(dependents.asSequence())
|
||||
|
||||
internal val dependentCaches: Iterable<IncrementalCacheImpl<Target>>
|
||||
get() = dependents
|
||||
|
||||
override fun registerInline(fromPath: String, jvmSignature: String, toPath: String) {
|
||||
}
|
||||
|
||||
protected open fun debugLog(message: String) {}
|
||||
|
||||
fun addDependentCache(cache: IncrementalCacheImpl<Target>) {
|
||||
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<Target>): 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
|
||||
|
||||
debugLog("$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)) }
|
||||
debugLog("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)
|
||||
}
|
||||
debugLog("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()
|
||||
normalCacheVersion(targetDataRoot).clean()
|
||||
experimentalCacheVersion(targetDataRoot).clean()
|
||||
}
|
||||
|
||||
fun cleanExperimental() {
|
||||
experimentalCacheVersion(targetDataRoot).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
|
||||
}
|
||||
|
||||
inner class SourceToClassesMap(storageFile: File) : BasicStringMap<Collection<String>>(storageFile, PathStringDescriptor, 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()
|
||||
|
||||
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)}]"
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
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(
|
||||
private val caches: Map<TargetId, IncrementalCache>,
|
||||
private val lookupTracker: LookupTracker
|
||||
): IncrementalCompilationComponents {
|
||||
override fun getIncrementalCache(target: TargetId): IncrementalCache =
|
||||
caches[target] ?: throw Exception("Incremental cache for target ${target.name} not found")
|
||||
|
||||
override fun getLookupTracker(): LookupTracker = lookupTracker
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
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"
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
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.incremental.storage.*
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
|
||||
open 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 addAll(lookups: Set<Map.Entry<LookupSymbol, Collection<String>>>) {
|
||||
val allPaths = lookups.flatMapTo(HashSet<String>()) { it.value }
|
||||
val pathToId = allPaths.keysToMap { addFileIfNeeded(File(it)) }
|
||||
|
||||
for ((lookupSymbol, paths) in lookups) {
|
||||
val key = LookupSymbolKey(lookupSymbol.name, lookupSymbol.scope)
|
||||
val fileIds = paths.mapTo(HashSet<Int>()) { pathToId[it]!! }
|
||||
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
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental
|
||||
|
||||
import com.google.protobuf.MessageLite
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.incremental.ProtoCompareGenerated.ProtoBufClassKind
|
||||
import org.jetbrains.kotlin.incremental.ProtoCompareGenerated.ProtoBufPackageKind
|
||||
import org.jetbrains.kotlin.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)
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
import com.intellij.util.io.DataExternalizer
|
||||
import 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
|
||||
|
||||
abstract class BasicMap<K : Comparable<K>, V>(
|
||||
storageFile: File,
|
||||
keyDescriptor: KeyDescriptor<K>,
|
||||
valueExternalizer: DataExternalizer<V>
|
||||
) {
|
||||
protected val storage = LazyStorage(storageFile, keyDescriptor, valueExternalizer)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
open class BasicMapsOwner {
|
||||
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
|
||||
}
|
||||
|
||||
open fun clean() {
|
||||
maps.forEach { it.clean() }
|
||||
}
|
||||
|
||||
open fun close() {
|
||||
maps.forEach { it.close() }
|
||||
}
|
||||
|
||||
open fun flush(memoryCachesOnly: Boolean) {
|
||||
maps.forEach { it.flush(memoryCachesOnly) }
|
||||
}
|
||||
|
||||
@TestOnly fun dump(): String = maps.map { it.dump() }.joinToString("\n\n")
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
import org.jetbrains.kotlin.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)
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
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]!! }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
import com.intellij.util.io.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
import com.intellij.util.io.DataExternalizer
|
||||
import 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
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
import 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
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
import com.intellij.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 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 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 PathStringDescriptor : EnumeratorStringDescriptor() {
|
||||
override fun getHashCode(value: String) = FileUtil.pathHashCode(value)
|
||||
|
||||
override fun isEqual(val1: String, val2: String?) = FileUtil.pathsEqual(val1, val2)
|
||||
}
|
||||
|
||||
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 IntCollectionExternalizer : CollectionExternalizer<Int>(IntExternalizer, { HashSet() })
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.incremental.storage
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
data class ProtoMapValue(val isPackageFacade: Boolean, val bytes: ByteArray, val strings: Array<String>)
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.modules
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
|
||||
import com.intellij.openapi.util.text.StringUtil.escapeXml
|
||||
import org.jetbrains.kotlin.build.JvmSourceRoot
|
||||
import org.jetbrains.kotlin.cli.common.modules.ModuleXmlParser.*
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
|
||||
class KotlinModuleXmlBuilder {
|
||||
private val xml = StringBuilder()
|
||||
private val p = Printer(xml)
|
||||
private var done = false
|
||||
|
||||
init {
|
||||
openTag(p, MODULES)
|
||||
}
|
||||
|
||||
fun addModule(
|
||||
moduleName: String,
|
||||
outputDir: String,
|
||||
sourceFiles: Iterable<File>,
|
||||
javaSourceRoots: Iterable<JvmSourceRoot>,
|
||||
classpathRoots: Iterable<File>,
|
||||
targetTypeId: String,
|
||||
isTests: Boolean,
|
||||
directoriesToFilterOut: Set<File>,
|
||||
friendDirs: Iterable<File>): KotlinModuleXmlBuilder {
|
||||
assert(!done) { "Already done" }
|
||||
|
||||
p.println("<!-- Module script for ${if (isTests) "tests" else "production"} -->")
|
||||
|
||||
p.println("<", MODULE, " ",
|
||||
NAME, "=\"", escapeXml(moduleName), "\" ",
|
||||
TYPE, "=\"", escapeXml(targetTypeId), "\" ",
|
||||
OUTPUT_DIR, "=\"", getEscapedPath(File(outputDir)), "\">")
|
||||
p.pushIndent()
|
||||
|
||||
for (friendDir in friendDirs) {
|
||||
p.println("<", FRIEND_DIR, " ", PATH, "=\"", getEscapedPath(friendDir), "\"/>")
|
||||
}
|
||||
|
||||
for (sourceFile in sourceFiles) {
|
||||
p.println("<", SOURCES, " ", PATH, "=\"", getEscapedPath(sourceFile), "\"/>")
|
||||
}
|
||||
|
||||
processJavaSourceRoots(javaSourceRoots)
|
||||
processClasspath(classpathRoots, directoriesToFilterOut)
|
||||
|
||||
closeTag(p, MODULE)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun processClasspath(
|
||||
files: Iterable<File>,
|
||||
directoriesToFilterOut: Set<File>) {
|
||||
p.println("<!-- Classpath -->")
|
||||
for (file in files) {
|
||||
val isOutput = directoriesToFilterOut.contains(file) && !IncrementalCompilation.isEnabled()
|
||||
if (isOutput) {
|
||||
// For IDEA's make (incremental compilation) purposes, output directories of the current module and its dependencies
|
||||
// appear on the class path, so we are at risk of seeing the results of the previous build, i.e. if some class was
|
||||
// removed in the sources, it may still be there in binaries. Thus, we delete these entries from the classpath.
|
||||
p.println("<!-- Output directory, commented out -->")
|
||||
p.println("<!-- ")
|
||||
p.pushIndent()
|
||||
}
|
||||
|
||||
p.println("<", CLASSPATH, " ", PATH, "=\"", getEscapedPath(file), "\"/>")
|
||||
|
||||
if (isOutput) {
|
||||
p.popIndent()
|
||||
p.println("-->")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processJavaSourceRoots(roots: Iterable<JvmSourceRoot>) {
|
||||
p.println("<!-- Java source roots -->")
|
||||
for (root in roots) {
|
||||
p.print("<")
|
||||
p.printWithNoIndent(JAVA_SOURCE_ROOTS, " ", PATH, "=\"", getEscapedPath(root.file), "\"")
|
||||
|
||||
if (root.packagePrefix != null) {
|
||||
p.printWithNoIndent(" ", JAVA_SOURCE_PACKAGE_PREFIX, "=\"", root.packagePrefix, "\"")
|
||||
}
|
||||
|
||||
p.printWithNoIndent("/>")
|
||||
p.println()
|
||||
}
|
||||
}
|
||||
|
||||
fun asText(): CharSequence {
|
||||
if (!done) {
|
||||
closeTag(p, MODULES)
|
||||
done = true
|
||||
}
|
||||
return xml
|
||||
}
|
||||
|
||||
private fun openTag(p: Printer, tag: String) {
|
||||
p.println("<$tag>")
|
||||
p.pushIndent()
|
||||
}
|
||||
|
||||
private fun closeTag(p: Printer, tag: String) {
|
||||
p.popIndent()
|
||||
p.println("</$tag>")
|
||||
}
|
||||
|
||||
private fun getEscapedPath(sourceFile: File): String {
|
||||
return escapeXml(toSystemIndependentName(sourceFile.path))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user