Gradle, ProcessedFilesCache: optimize serialization

JSON kept for debugability
This commit is contained in:
Sergey Rostov
2019-07-03 13:49:59 +03:00
parent dff48b251d
commit 9679403831
2 changed files with 104 additions and 23 deletions
@@ -6,7 +6,9 @@
package org.jetbrains.kotlin.gradle.internal
import com.google.gson.GsonBuilder
import com.google.gson.JsonParseException
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import org.gradle.api.Project
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.hash.FileHasher
@@ -15,8 +17,8 @@ import java.io.File
/**
* Cache for preventing processing some files twice.
* Files are precessed some subdirectories of [targetDir] called `target`.
* For each target [getOrCompute] should be called.
* Files are precessed to some subdirectories of [targetDir] called `target`.
* [getOrCompute] returns path to directory with files produced for given input file.
* Absent target directories will be deleted on [close].
* `compute` will be called only for source files that was changed since last processing.
*
@@ -33,16 +35,104 @@ internal open class ProcessedFilesCache(
private val hasher = (project as ProjectInternal).services.get(FileHasher::class.java)
private val gson = GsonBuilder().setPrettyPrinting().create()
private fun readFrom(json: JsonReader): State? {
val result = State()
json.obj {
check(json.nextName() == "version")
val version = json.nextString()
if (version != this.version) return null
check(json.nextName() == "items")
json.obj {
while (json.peek() == JsonToken.NAME) {
val key = json.nextName()
json.beginObject()
check(json.nextName() == "src")
val src = json.nextString()
var target: String? = null
if (json.peek() == JsonToken.NAME) {
check(json.nextName() == "target")
if (json.peek() != JsonToken.NULL) {
target = json.nextString()
}
}
json.endObject()
result[decodeHexString(key)] = Element(src, target)
}
}
}
return result
}
private fun State.writeTo(json: JsonWriter) {
json.obj {
json.name("version").value(version)
json.name("items")
json.obj {
byHash.forEach {
json.name(it.key.toHex())
json.obj {
json.name("src").value(it.value.src)
json.name("target")
if (it.value.target == null) json.nullValue() else json.value(it.value.target)
}
}
}
}
}
private inline fun JsonReader.obj(body: () -> Unit) {
beginObject()
body()
endObject()
}
private inline fun JsonWriter.obj(body: () -> Unit) {
beginObject()
body()
endObject()
}
fun ByteArray.toHex(): String {
val result = CharArray(size * 2) { ' ' }
var i = 0
forEach {
val n = it.toInt()
result[i++] = Character.forDigit(n shr 4 and 0xF, 16)
result[i++] = Character.forDigit(n and 0xF, 16)
}
return String(result)
}
private fun decodeHexString(hexString: String): ByteArray {
check(hexString.length % 2 == 0)
val bytes = ByteArray(hexString.length / 2)
var i = 0
var o = 0
while (i < hexString.length) {
bytes[o++] = hexToByte(hexString[i++], hexString[i++])
}
return bytes
}
private fun hexToByte(a: Char, b: Char): Byte = ((a.toDigit() shl 4) + b.toDigit()).toByte()
private fun Char.toDigit(): Int = Character.digit(this, 16).also { check(it != -1) }
private class State {
var version: String? = null
val byHash = mutableMapOf<String, Element>()
val byHash = mutableMapOf<ByteArray, Element>()
val byTarget = mutableMapOf<String, Element>()
operator fun get(elementHash: ByteArray) =
byHash[elementHash.toHexString()]
operator fun get(elementHash: ByteArray) = byHash[elementHash]
operator fun set(elementHash: ByteArray, element: Element) {
byHash[elementHash.toHexString()] = element
elementHash.toHexString()
byHash[elementHash] = element
val target = element.target
if (target != null) {
byTarget[target] = element
@@ -62,15 +152,10 @@ internal open class ProcessedFilesCache(
targetDir.mkdirs()
val state: State? = if (stateFile.exists()) try {
stateFile.reader().use {
gson.fromJson(it, State::class.java)
}.let {
if (it.version != version) {
targetDir.deleteRecursively()
null
} else it
gson.newJsonReader(stateFile.reader()).use {
readFrom(it)
}
} catch (e: JsonParseException) {
} catch (e: Throwable) {
project.logger.warn("Cannot read $stateFile", e)
if (targetDir.exists()) {
targetDir.deleteRecursively()
@@ -83,10 +168,6 @@ internal open class ProcessedFilesCache(
private val new = State()
init {
new.version = version
}
internal fun getOrCompute(
file: File,
compute: () -> String?
@@ -144,8 +225,8 @@ internal open class ProcessedFilesCache(
}
stateFile.parentFile.mkdirs()
stateFile.writer().use {
gson.toJson(new, it)
gson.newJsonWriter(stateFile.writer()).use {
new.writeTo(it)
}
}
}
@@ -21,7 +21,7 @@ internal class GradleNodeModulesCache(val nodeJs: NodeJsRootExtension) : AutoClo
val project: Project get() = nodeJs.project
internal val dir = nodeJs.root.nodeModulesGradleCacheDir
private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "8")
private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "9")
@Synchronized
fun get(