Create Kotlin incremental caches only on write

Original commit: fe390a04c5
This commit is contained in:
Alexey Tsvetkov
2015-09-15 17:25:28 +03:00
parent e810c6f007
commit eabfd142f3
10 changed files with 217 additions and 71 deletions
@@ -92,7 +92,7 @@ class CacheFormatVersion(targetDataRoot: File) {
} }
fun saveIfNeeded() { fun saveIfNeeded() {
if (!file.exists()) { if (file.parentFile.exists() && !file.exists()) {
file.writeText(actualCacheFormatVersion().toString()) file.writeText(actualCacheFormatVersion().toString())
} }
} }
@@ -271,9 +271,8 @@ public class IncrementalCacheImpl(
.toList() .toList()
val changesInfo = dirtyClasses.fold(ChangesInfo.NO_CHANGES) { info, className -> val changesInfo = dirtyClasses.fold(ChangesInfo.NO_CHANGES) { info, className ->
val internalName = className.internalName val newInfo = ChangesInfo(protoChanged = className in protoMap,
val newInfo = ChangesInfo(protoChanged = internalName in protoMap, constantsChanged = className in constantsMap)
constantsChanged = internalName in constantsMap)
newInfo.logIfSomethingChanged(className) newInfo.logIfSomethingChanged(className)
info + newInfo info + newInfo
} }
@@ -334,7 +333,7 @@ public class IncrementalCacheImpl(
val data = ProtoMapValue(isPackage, bytes) val data = ProtoMapValue(isPackage, bytes)
if (oldData == null || !Arrays.equals(bytes, oldData.bytes) || isPackage != oldData.isPackageFacade) { if (oldData == null || !Arrays.equals(bytes, oldData.bytes) || isPackage != oldData.isPackageFacade) {
storage.put(key, data) storage[key] = data
} }
return ChangesInfo(protoChanged = oldData == null || return ChangesInfo(protoChanged = oldData == null ||
@@ -342,12 +341,14 @@ public class IncrementalCacheImpl(
difference(oldData, data) != DifferenceKind.NONE) difference(oldData, data) != DifferenceKind.NONE)
} }
public fun get(className: JvmClassName): ProtoMapValue? { public fun contains(className: JvmClassName): Boolean =
return storage[className.getInternalName()] className.internalName in storage
}
public fun get(className: JvmClassName): ProtoMapValue? =
storage[className.internalName]
public fun remove(className: JvmClassName) { public fun remove(className: JvmClassName) {
storage.remove(className.getInternalName()) storage.remove(className.internalName)
} }
override fun dumpValue(value: ProtoMapValue): String { override fun dumpValue(value: ProtoMapValue): String {
@@ -372,6 +373,9 @@ public class IncrementalCacheImpl(
return if (result.isEmpty()) null else result return if (result.isEmpty()) null else result
} }
fun contains(className: JvmClassName): Boolean =
className.internalName in storage
public fun process(kotlinClass: LocalFileKotlinClass): ChangesInfo { public fun process(kotlinClass: LocalFileKotlinClass): ChangesInfo {
return put(kotlinClass.className, getConstantsMap(kotlinClass.fileContents)) return put(kotlinClass.className, getConstantsMap(kotlinClass.fileContents))
} }
@@ -383,7 +387,7 @@ public class IncrementalCacheImpl(
if (oldMap == constantsMap) return ChangesInfo.NO_CHANGES if (oldMap == constantsMap) return ChangesInfo.NO_CHANGES
if (constantsMap != null) { if (constantsMap != null) {
storage.put(key, constantsMap) storage[key] = constantsMap
} }
else { else {
storage.remove(key) storage.remove(key)
@@ -514,7 +518,7 @@ public class IncrementalCacheImpl(
} }
when { when {
newMap.isNotEmpty() -> storage.put(internalName, newMap) newMap.isNotEmpty() -> storage[internalName] = newMap
else -> storage.remove(internalName) else -> storage.remove(internalName)
} }
@@ -536,48 +540,46 @@ public class IncrementalCacheImpl(
private inner class PackagePartMap(storageFile: File) : BasicStringMap<Boolean>(storageFile, BooleanDataDescriptor.INSTANCE) { private inner class PackagePartMap(storageFile: File) : BasicStringMap<Boolean>(storageFile, BooleanDataDescriptor.INSTANCE) {
public fun addPackagePart(className: JvmClassName) { public fun addPackagePart(className: JvmClassName) {
storage.put(className.getInternalName(), true) storage[className.internalName] = true
} }
public fun remove(className: JvmClassName) { public fun remove(className: JvmClassName) {
storage.remove(className.getInternalName()) storage.remove(className.internalName)
} }
public fun isPackagePart(className: JvmClassName): Boolean { public fun isPackagePart(className: JvmClassName): Boolean =
return storage.containsMapping(className.getInternalName()) className.internalName in storage
}
override fun dumpValue(value: Boolean) = "" override fun dumpValue(value: Boolean) = ""
} }
private inner class SourceToClassesMap(storageFile: File) : BasicStringMap<List<String>>(storageFile, PathStringDescriptor.INSTANCE, StringListExternalizer) { private inner class SourceToClassesMap(storageFile: File) : BasicStringMap<List<String>>(storageFile, PathStringDescriptor.INSTANCE, StringListExternalizer) {
public fun clearOutputsForSource(sourceFile: File) { public fun clearOutputsForSource(sourceFile: File) {
storage.remove(sourceFile.getAbsolutePath()) storage.remove(sourceFile.absolutePath)
} }
public fun add(sourceFile: File, className: JvmClassName) { public fun add(sourceFile: File, className: JvmClassName) {
storage.appendData(sourceFile.getAbsolutePath(), { out -> IOUtil.writeUTF(out, className.getInternalName()) }) storage.append(sourceFile.absolutePath, { out -> IOUtil.writeUTF(out, className.getInternalName()) })
} }
public fun get(sourceFile: File): Collection<JvmClassName> { public fun get(sourceFile: File): Collection<JvmClassName> =
return storage[sourceFile.getAbsolutePath()].orEmpty().map { JvmClassName.byInternalName(it) } storage[sourceFile.absolutePath].orEmpty().map { JvmClassName.byInternalName(it) }
}
override fun dumpValue(value: List<String>) = value.toString() override fun dumpValue(value: List<String>) = value.toString()
} }
private inner class ClassToSourcesMap(storageFile: File) : BasicStringMap<Collection<String>>(storageFile, PathCollectionExternalizer) { private inner class ClassToSourcesMap(storageFile: File) : BasicStringMap<Collection<String>>(storageFile, PathCollectionExternalizer) {
public fun get(className: JvmClassName): Collection<String> = public fun get(className: JvmClassName): Collection<String> =
storage[className.internalName] ?: emptySet() getFromStorage(className.internalName) ?: emptySet()
public fun add(className: JvmClassName, sourceFile: File) { public fun add(className: JvmClassName, sourceFile: File) {
storage.appendData(className.internalName) { out -> appendDataToStorage(className.internalName) { out ->
IOUtil.writeUTF(out, sourceFile.normalizedPath) IOUtil.writeUTF(out, sourceFile.normalizedPath)
} }
} }
public fun remove(className: JvmClassName) { public fun remove(className: JvmClassName) {
storage.remove(className.internalName) removeFromStorage(className.internalName)
} }
override fun dumpValue(value: Collection<String>): String = override fun dumpValue(value: Collection<String>): String =
@@ -586,27 +588,25 @@ public class IncrementalCacheImpl(
private inner class DirtyOutputClassesMap(storageFile: File) : BasicStringMap<Boolean>(storageFile, BooleanDataDescriptor.INSTANCE) { private inner class DirtyOutputClassesMap(storageFile: File) : BasicStringMap<Boolean>(storageFile, BooleanDataDescriptor.INSTANCE) {
public fun markDirty(className: String) { public fun markDirty(className: String) {
storage.put(className, true) storage[className] = true
} }
public fun notDirty(className: String) { public fun notDirty(className: String) {
storage.remove(className) storage.remove(className)
} }
public fun getDirtyOutputClasses(): Collection<String> { public fun getDirtyOutputClasses(): Collection<String> =
return storage.getAllKeysWithExistingMapping() storage.keys
}
override fun dumpValue(value: Boolean) = "" override fun dumpValue(value: Boolean) = ""
} }
private inner class DirtyInlineFunctionsMap(storageFile: File) : BasicStringMap<List<String>>(storageFile, StringListExternalizer) { private inner class DirtyInlineFunctionsMap(storageFile: File) : BasicStringMap<List<String>>(storageFile, StringListExternalizer) {
public fun getEntries(): Map<JvmClassName, List<String>> = public fun getEntries(): Map<JvmClassName, List<String>> =
storage.allKeysWithExistingMapping storage.keys.toMap(JvmClassName::byInternalName) { storage[it]!! }
.toMap(JvmClassName::byInternalName) { storage[it] }
public fun put(className: JvmClassName, changedFunctions: List<String>) { public fun put(className: JvmClassName, changedFunctions: List<String>) {
storage.put(className.internalName, changedFunctions) storage[className.internalName] = changedFunctions
} }
override fun dumpValue(value: List<String>) = override fun dumpValue(value: List<String>) =
@@ -625,7 +625,7 @@ public class IncrementalCacheImpl(
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, PathCollectionExternalizer) {
public fun add(sourcePath: String, jvmSignature: String, targetPath: String) { public fun add(sourcePath: String, jvmSignature: String, targetPath: String) {
val key = PathFunctionPair(sourcePath, jvmSignature) val key = PathFunctionPair(sourcePath, jvmSignature)
storage.appendData(key) { out -> storage.append(key) { out ->
IOUtil.writeUTF(out, targetPath) IOUtil.writeUTF(out, targetPath)
} }
} }
@@ -19,60 +19,38 @@ package org.jetbrains.kotlin.jps.incremental.storage
import com.intellij.util.io.DataExternalizer import com.intellij.util.io.DataExternalizer
import com.intellij.util.io.EnumeratorStringDescriptor import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor import com.intellij.util.io.KeyDescriptor
import com.intellij.util.io.PersistentHashMap
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.utils.Printer import org.jetbrains.kotlin.utils.Printer
import java.io.File import java.io.File
import java.io.IOException
public abstract class BasicMap<K : Comparable<K>, V>( abstract class BasicMap<K : Comparable<K>, V>(
private val storageFile: File, storageFile: File,
private val keyDescriptor: KeyDescriptor<K>, keyDescriptor: KeyDescriptor<K>,
private val valueExternalizer: DataExternalizer<V> valueExternalizer: DataExternalizer<V>
) { ) {
protected var storage: PersistentHashMap<K, V> = createMap() protected val storage = LazyStorage(storageFile, keyDescriptor, valueExternalizer)
public fun contains(key: K): Boolean = storage.containsMapping(key) fun clean() {
storage.clean()
public fun clean() {
try {
storage.close()
}
catch (ignored: IOException) {
}
PersistentHashMap.deleteFilesStartingWith(storage.getBaseFile()!!)
try {
storage = createMap()
}
catch (ignored: IOException) {
}
} }
public fun flush(memoryCachesOnly: Boolean) { fun flush(memoryCachesOnly: Boolean) {
if (memoryCachesOnly) { storage.flush(memoryCachesOnly)
if (storage.isDirty()) {
storage.dropMemoryCaches()
}
}
else {
storage.force()
}
} }
public fun close() { fun close() {
storage.close() storage.close()
} }
@TestOnly @TestOnly
public fun dump(): String { fun dump(): String {
return with(StringBuilder()) { return with(StringBuilder()) {
with(Printer(this)) { with(Printer(this)) {
println(this@BasicMap.javaClass.getSimpleName()) println(this@BasicMap.javaClass.simpleName)
pushIndent() pushIndent()
for (key in storage.getAllKeysWithExistingMapping().sort()) { for (key in storage.keys.sorted()) {
println("${dumpKey(key)} -> ${dumpValue(storage[key])}") println("${dumpKey(key)} -> ${dumpValue(storage[key]!!)}")
} }
popIndent() popIndent()
@@ -84,9 +62,6 @@ public abstract class BasicMap<K : Comparable<K>, V>(
protected abstract fun dumpKey(key: K): String protected abstract fun dumpKey(key: K): String
protected abstract fun dumpValue(value: V): String protected abstract fun dumpValue(value: V): String
private fun createMap(): PersistentHashMap<K, V> =
PersistentHashMap(storageFile, keyDescriptor, valueExternalizer)
} }
public abstract class BasicStringMap<V>( public abstract class BasicStringMap<V>(
@@ -0,0 +1,113 @@
/*
* 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.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()
fun contains(key: K): Boolean =
getStorageIfExists()?.containsMapping(key) ?: false
fun get(key: K): V? =
getStorageIfExists()?.get(key)
fun set(key: K, value: V) {
getStorageOrCreateNew().put(key, value)
}
fun remove(key: K) {
getStorageIfExists()?.remove(key)
}
fun append(key: K, append: (DataOutput)->Unit) {
getStorageOrCreateNew().appendData(key, append)
}
@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)
}
@@ -651,6 +651,17 @@ public class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
assertFalse(File(storageRoot, "targets/java-production/kotlinProject/kotlin").exists()) assertFalse(File(storageRoot, "targets/java-production/kotlinProject/kotlin").exists())
} }
public fun testDoNotCreateUselessKotlinIncrementalCachesForDependentTargets() {
initProject()
makeAll().assertSuccessful()
checkWhen(touch("src/utils.kt"), null, packageClasses("kotlinProject", "src/utils.kt", "_DefaultPackage"))
val storageRoot = BuildDataPathsImpl(myDataStorageRoot).dataStorageRoot
assertTrue(File(storageRoot, "targets/java-production/kotlinProject/kotlin").exists())
assertFalse(File(storageRoot, "targets/java-production/module2/kotlin").exists())
}
private fun buildCustom(canceledStatus: CanceledStatus, logger: TestProjectBuilderLogger,buildResult: BuildResult) { private fun buildCustom(canceledStatus: CanceledStatus, logger: TestProjectBuilderLogger,buildResult: BuildResult) {
val scopeBuilder = CompileScopeTestBuilder.make().all() val scopeBuilder = CompileScopeTestBuilder.make().all()
val descriptor = this.createProjectDescriptor(BuildLoggingManager(logger)) val descriptor = this.createProjectDescriptor(BuildLoggingManager(logger))
@@ -0,0 +1,13 @@
<?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="jdk" jdkName="IDEA_JDK" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinRuntime" level="project" />
</component>
</module>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac" />
</component>
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/kotlinProject.iml" filepath="$PROJECT_DIR$/kotlinProject.iml" />
<module fileurl="file://$PROJECT_DIR$/module2/module2.iml" filepath="$PROJECT_DIR$/module2/module2.iml" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="IDEA_JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -0,0 +1,14 @@
<?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="jdk" jdkName="IDEA_JDK" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="kotlinProject" />
<orderEntry type="library" name="KotlinRuntime" level="project" />
</component>
</module>