Gradle, ProcessedFilesCache: use file as target type (not string)

recompute removed target directories
This commit is contained in:
Sergey Rostov
2019-07-03 15:49:27 +03:00
parent 9679403831
commit fd2d99e205
2 changed files with 59 additions and 64 deletions
@@ -19,7 +19,6 @@ import java.io.File
* Cache for preventing processing some files twice.
* 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.
*
* See [org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModuleBuilder] for example.
@@ -74,7 +73,7 @@ internal open class ProcessedFilesCache(
json.name("items")
json.obj {
byHash.forEach {
json.name(it.key.toHex())
json.name(it.key.contents.toHex())
json.obj {
json.name("src").value(it.value.src)
json.name("target")
@@ -124,15 +123,32 @@ internal open class ProcessedFilesCache(
private fun Char.toDigit(): Int = Character.digit(this, 16).also { check(it != -1) }
class ByteArrayWrapper(val contents: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ByteArrayWrapper
if (!contents.contentEquals(other.contents)) return false
return true
}
override fun hashCode(): Int {
return contents.contentHashCode()
}
}
private class State {
val byHash = mutableMapOf<ByteArray, Element>()
val byHash = mutableMapOf<ByteArrayWrapper, Element>()
val byTarget = mutableMapOf<String, Element>()
operator fun get(elementHash: ByteArray) = byHash[elementHash]
operator fun get(elementHash: ByteArray) = byHash[ByteArrayWrapper(elementHash)]
operator fun set(elementHash: ByteArray, element: Element) {
elementHash.toHexString()
byHash[elementHash] = element
byHash[ByteArrayWrapper(elementHash)] = element
val target = element.target
if (target != null) {
byTarget[target] = element
@@ -146,87 +162,65 @@ internal open class ProcessedFilesCache(
)
private val stateFile = targetDir.resolve(stateFileName)
private val old: State
private val state: State
init {
targetDir.mkdirs()
val state: State? = if (stateFile.exists()) try {
gson.newJsonReader(stateFile.reader()).use {
readFrom(it)
state = (if (stateFile.exists()) {
try {
gson.newJsonReader(stateFile.reader()).use { readFrom(it) }
} catch (e: Throwable) {
project.logger.warn("Cannot read $stateFile", e)
if (targetDir.exists()) {
targetDir.deleteRecursively()
}
null
}
} catch (e: Throwable) {
project.logger.warn("Cannot read $stateFile", e)
if (targetDir.exists()) {
targetDir.deleteRecursively()
}
null
} else null
old = state ?: State()
} else null) ?: State()
}
private val new = State()
internal fun getOrCompute(
file: File,
compute: () -> String?
compute: () -> File?
): File? = getOrComputeKey(file, compute)?.let { File(targetDir, it) }
private fun getOrComputeKey(
file: File,
compute: () -> File?
): String? {
val hash = hasher.hash(file).toByteArray()
val old = old[hash]
val old = state[hash]
if (old != null) {
new[hash] = old
return old.target
} else {
val key = compute()
val existedTarget = new.byTarget[key]
if (key != null && existedTarget != null) {
if (existedTarget.src != file.canonicalPath) {
reportTargetClash(key, file, File(existedTarget.src))
}
}
new[hash] = Element(file.canonicalPath, key)
return key
if (checkTarget(old.target)) return old.target
else project.logger.warn("Cannot find ${File(targetDir.relativeTo(project.projectDir), old.target!!)}, rebuilding")
}
val key = compute()?.relativeTo(targetDir)?.toString()
val existedTarget = state.byTarget[key]
if (key != null && existedTarget != null) {
if (existedTarget.src != file.canonicalPath) {
reportTargetClash(key, file, File(existedTarget.src))
}
}
state[hash] = Element(file.canonicalPath, key)
return key
}
fun deleteTarget(key: String) {
targetDir.resolve(key).deleteRecursively()
fun checkTarget(target: String?): Boolean {
if (target == null) return true
return targetDir.resolve(target).exists()
}
val targets: Collection<String>
get() = new.byHash.mapNotNullTo(mutableSetOf()) { it.value.target }
protected open fun reportTargetClash(target: String, existedSrc: File, newSrc: File): Nothing =
error("Both `$existedSrc` and `$newSrc` produces `$target`")
private fun getDeletedTargets(): MutableSet<String> {
val result = mutableSetOf<String>()
old.byHash.forEach {
val target = it.value.target
if (target != null) {
result.add(target)
}
}
new.byHash.forEach {
val target = it.value.target
if (target != null) {
result.remove(target)
}
}
return result
}
override fun close() {
getDeletedTargets().forEach {
deleteTarget(it)
}
stateFile.parentFile.mkdirs()
gson.newJsonWriter(stateFile.writer()).use {
new.writeTo(it)
state.writeTo(it)
}
}
}
@@ -190,7 +190,8 @@ internal class KotlinCompilationNpmResolver(
fun createPackageJson(): KotlinCompilationNpmResolution {
val resolvedInternalDependencies = internalDependencies.map {
it.resolution ?: error("Unresolved dependent npm package: ${this@KotlinCompilationNpmResolver} -> $it")
it.resolution
?: error("Unresolved dependent npm package: ${this@KotlinCompilationNpmResolver} -> $it")
}
val importedExternalGradleDependencies = externalGradleDependencies.mapNotNull {
resolver.gradleNodeModules.get(it.dependency, it.artifact)