[Gradle, JS] Next step of configurstion cache

This commit is contained in:
Ilya Goncharov
2020-10-16 14:46:07 +03:00
committed by Alexander Likhachev
parent 19e59fe770
commit 60da9281a2
7 changed files with 64 additions and 62 deletions
@@ -9,9 +9,7 @@ import com.google.gson.GsonBuilder
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import org.gradle.api.Project import org.gradle.api.logging.Logger
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.hash.FileHasher
import java.io.File import java.io.File
/** /**
@@ -24,14 +22,15 @@ import java.io.File
* *
* @param version When updating logic in `compute`, `version` should be increased to invalidate cache * @param version When updating logic in `compute`, `version` should be increased to invalidate cache
*/ */
// CHECK
internal open class ProcessedFilesCache( internal open class ProcessedFilesCache(
val project: Project, val logger: Logger,
// val hasher: FileHasher,
val projectDir: File,
val targetDir: File, val targetDir: File,
stateFileName: String, stateFileName: String,
val version: String val version: String
) : AutoCloseable { ) : AutoCloseable {
private val hasher = (project as ProjectInternal).services.get(FileHasher::class.java)
private val gson = GsonBuilder().setPrettyPrinting().create()
private fun readFrom(json: JsonReader): State? { private fun readFrom(json: JsonReader): State? {
val result = State() val result = State()
@@ -72,7 +71,7 @@ internal open class ProcessedFilesCache(
json.name("items") json.name("items")
json.obj { json.obj {
byHash.forEach { byHash.forEach {
json.name(it.key.contents.toHex()) json.name(it.key.toHex())
json.obj { json.obj {
json.name("src").value(it.value.src) json.name("src").value(it.value.src)
json.name("target") json.name("target")
@@ -96,26 +95,12 @@ internal open class ProcessedFilesCache(
endObject() endObject()
} }
fun ByteArray.toHex(): String { fun Int.toHex(): String {
val result = CharArray(size * 2) { ' ' } return toString(16)
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 { private fun decodeHexString(hexString: String): Int {
check(hexString.length % 2 == 0) return Integer.parseInt(hexString, 16)
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 hexToByte(a: Char, b: Char): Byte = ((a.toDigit() shl 4) + b.toDigit()).toByte()
@@ -140,13 +125,13 @@ internal open class ProcessedFilesCache(
} }
private class State { private class State {
val byHash = mutableMapOf<ByteArrayWrapper, Element>() val byHash = mutableMapOf<Int, Element>()
val byTarget = mutableMapOf<String, Element>() val byTarget = mutableMapOf<String, Element>()
operator fun get(elementHash: ByteArray) = byHash[ByteArrayWrapper(elementHash)] operator fun get(elementHash: Int) = byHash[elementHash]
operator fun set(elementHash: ByteArray, element: Element) { operator fun set(elementHash: Int, element: Element) {
byHash[ByteArrayWrapper(elementHash)] = element byHash[elementHash] = element
val target = element.target val target = element.target
if (target != null) { if (target != null) {
byTarget[target] = element byTarget[target] = element
@@ -175,9 +160,9 @@ internal open class ProcessedFilesCache(
state = (if (stateFile.exists()) { state = (if (stateFile.exists()) {
try { try {
gson.newJsonReader(stateFile.reader()).use { readFrom(it) } GsonBuilder().setPrettyPrinting().create().newJsonReader(stateFile.reader()).use { readFrom(it) }
} catch (e: Throwable) { } catch (e: Throwable) {
project.logger.warn("Cannot read $stateFile", e) logger.warn("Cannot read $stateFile", e)
if (targetDir.exists()) { if (targetDir.exists()) {
targetDir.deleteRecursively() targetDir.deleteRecursively()
} }
@@ -195,19 +180,19 @@ internal open class ProcessedFilesCache(
file: File, file: File,
compute: () -> File? compute: () -> File?
): String? { ): String? {
val hash = hasher.hash(file).toByteArray() val hash = file.hashCode()
val old = state[hash] val old = state[hash]
if (old != null) { if (old != null) {
if (checkTarget(old.target)) return old.target if (checkTarget(old.target)) return old.target
else project.logger.warn("Cannot find ${File(targetDir.relativeTo(project.projectDir), old.target!!)}, rebuilding") else logger.warn("Cannot find ${File(targetDir.relativeTo(projectDir), old.target!!)}, rebuilding")
} }
val key = compute()?.relativeTo(targetDir)?.toString() val key = compute()?.relativeTo(targetDir)?.toString()
val existedTarget = state.byTarget[key] val existedTarget = state.byTarget[key]
if (key != null && existedTarget != null) { if (key != null && existedTarget != null) {
if (!File(existedTarget.src).exists()) { if (!File(existedTarget.src).exists()) {
project.logger.warn("Removing cache for removed source `${existedTarget.src}`") logger.warn("Removing cache for removed source `${existedTarget.src}`")
state.remove(existedTarget) state.remove(existedTarget)
} }
} }
@@ -223,7 +208,7 @@ internal open class ProcessedFilesCache(
override fun close() { override fun close() {
stateFile.parentFile.mkdirs() stateFile.parentFile.mkdirs()
gson.newJsonWriter(stateFile.writer()).use { GsonBuilder().setPrettyPrinting().create().newJsonWriter(stateFile.writer()).use {
state.writeTo(it) state.writeTo(it)
} }
} }
@@ -6,8 +6,8 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import org.gradle.api.Project import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.artifacts.ResolvedDependency import org.gradle.internal.hash.FileHasher
import org.jetbrains.kotlin.gradle.internal.ProcessedFilesCache import org.jetbrains.kotlin.gradle.internal.ProcessedFilesCache
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import java.io.File import java.io.File
@@ -15,14 +15,20 @@ import java.io.File
/** /**
* Cache for storing already created [GradleNodeModule]s * Cache for storing already created [GradleNodeModule]s
*/ */
internal abstract class AbstractNodeModulesCache(val nodeJs: NodeJsRootExtension) : AutoCloseable { internal abstract class AbstractNodeModulesCache(nodeJs: NodeJsRootExtension) : AutoCloseable {
companion object { companion object {
const val STATE_FILE_NAME = ".visited" const val STATE_FILE_NAME = ".visited"
} }
val project: Project get() = nodeJs.rootProject
internal val dir = nodeJs.nodeModulesGradleCacheDir internal val dir = nodeJs.nodeModulesGradleCacheDir
private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "9") private val cache = ProcessedFilesCache(
nodeJs.rootProject.logger,
// (nodeJs.rootProject as ProjectInternal).services.get(FileHasher::class.java),
nodeJs.rootProject.projectDir,
dir,
STATE_FILE_NAME,
"9"
)
@Synchronized @Synchronized
fun get( fun get(
@@ -5,17 +5,14 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Project
import org.gradle.api.artifacts.ResolvedDependency
import java.io.File import java.io.File
/** /**
* Creates fake NodeJS module directory from given composite [dependency]. * Creates fake NodeJS module directory from given composite [dependency].
*/ */
internal class CompositeNodeModuleBuilder( internal class CompositeNodeModuleBuilder(
val project: Project,
val srcDir: File, val srcDir: File,
val cache: CompositeNodeModulesCache val cacheDir: File
) { ) {
var srcPackageJsonFile: File = srcDir var srcPackageJsonFile: File = srcDir
@@ -29,15 +26,14 @@ internal class CompositeNodeModuleBuilder(
// yarn requires semver // yarn requires semver
packageJson.version = fixSemver(packageJson.version) packageJson.version = fixSemver(packageJson.version)
val container = cache.dir val importedPackageDir = importedPackageDir(cacheDir, packageJson.name, packageJson.version)
val importedPackageDir = importedPackageDir(container, packageJson.name, packageJson.version)
packageJson.main = srcDir.parentFile.resolve(packageJson.main!!) packageJson.main = srcDir.parentFile.resolve(packageJson.main!!)
.relativeToOrNull(importedPackageDir) .relativeToOrNull(importedPackageDir)
?.path ?.path
?: throw IllegalStateException("Unable to link composite builds for Kotlin/JS which have different roots") ?: throw IllegalStateException("Unable to link composite builds for Kotlin/JS which have different roots")
return makeNodeModule(container, packageJson) return makeNodeModule(cacheDir, packageJson)
} }
} }
@@ -17,7 +17,7 @@ internal class CompositeNodeModulesCache(nodeJs: NodeJsRootExtension) : Abstract
version: String, version: String,
file: File file: File
): File? { ): File? {
val module = CompositeNodeModuleBuilder(project, file, this) val module = CompositeNodeModuleBuilder(file, dir)
return module.rebuild() return module.rebuild()
} }
} }
@@ -5,7 +5,8 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Project import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileSystemOperations
import org.jetbrains.kotlin.gradle.targets.js.JS import org.jetbrains.kotlin.gradle.targets.js.JS
import org.jetbrains.kotlin.gradle.targets.js.JS_MAP import org.jetbrains.kotlin.gradle.targets.js.JS_MAP
import org.jetbrains.kotlin.gradle.targets.js.META_JS import org.jetbrains.kotlin.gradle.targets.js.META_JS
@@ -16,11 +17,12 @@ import java.io.File
* Creates fake NodeJS module directory from given gradle [dependency]. * Creates fake NodeJS module directory from given gradle [dependency].
*/ */
internal class GradleNodeModuleBuilder( internal class GradleNodeModuleBuilder(
val project: Project, val fs: FileSystemOperations,
val archiveOperations: ArchiveOperations,
val moduleName: String, val moduleName: String,
val moduleVersion: String, val moduleVersion: String,
val srcFiles: Collection<File>, val srcFiles: Collection<File>,
val cache: GradleNodeModulesCache val cacheDir: File
) { ) {
private var srcPackageJsonFile: File? = null private var srcPackageJsonFile: File? = null
private val files = mutableListOf<File>() private val files = mutableListOf<File>()
@@ -29,7 +31,7 @@ internal class GradleNodeModuleBuilder(
srcFiles.forEach { srcFile -> srcFiles.forEach { srcFile ->
when { when {
isKotlinJsRuntimeFile(srcFile) -> files.add(srcFile) isKotlinJsRuntimeFile(srcFile) -> files.add(srcFile)
srcFile.isCompatibleArchive -> project.zipTree(srcFile).forEach { innerFile -> srcFile.isCompatibleArchive -> archiveOperations.zipTree(srcFile).forEach { innerFile ->
when { when {
innerFile.name == NpmProject.PACKAGE_JSON -> srcPackageJsonFile = innerFile innerFile.name == NpmProject.PACKAGE_JSON -> srcPackageJsonFile = innerFile
isKotlinJsRuntimeFile(innerFile) -> files.add(innerFile) isKotlinJsRuntimeFile(innerFile) -> files.add(innerFile)
@@ -61,8 +63,8 @@ internal class GradleNodeModuleBuilder(
val actualFiles = files.filterNot { it.name.endsWith(".$META_JS") } val actualFiles = files.filterNot { it.name.endsWith(".$META_JS") }
return makeNodeModule(cache.dir, packageJson) { nodeModule -> return makeNodeModule(cacheDir, packageJson) { nodeModule ->
project.copy { copy -> fs.copy { copy ->
copy.from(actualFiles) copy.from(actualFiles)
copy.into(nodeModule) copy.into(nodeModule)
} }
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.internal.project.ProjectInternal
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import java.io.File import java.io.File
@@ -13,12 +15,15 @@ import java.io.File
* Cache for storing already created [GradleNodeModule]s * Cache for storing already created [GradleNodeModule]s
*/ */
internal class GradleNodeModulesCache(nodeJs: NodeJsRootExtension) : AbstractNodeModulesCache(nodeJs) { internal class GradleNodeModulesCache(nodeJs: NodeJsRootExtension) : AbstractNodeModulesCache(nodeJs) {
private val fs = (nodeJs.rootProject as ProjectInternal).services.get(FileSystemOperations::class.java)
private val archiveOperations = (nodeJs.rootProject as ProjectInternal).services.get(ArchiveOperations::class.java)
override fun buildImportedPackage( override fun buildImportedPackage(
name: String, name: String,
version: String, version: String,
file: File file: File
): File? { ): File? {
val module = GradleNodeModuleBuilder(project, name, version, listOf(file), this) val module = GradleNodeModuleBuilder(fs, archiveOperations, name, version, listOf(file), dir)
module.visitArtifacts() module.visitArtifacts()
return module.rebuild() return module.rebuild()
} }
@@ -51,6 +51,14 @@ internal class KotlinCompilationNpmResolver(
@Transient @Transient
val resolver = projectResolver.resolver val resolver = projectResolver.resolver
private val gradleNodeModules by lazy {
resolver.gradleNodeModules
}
private val compositeNodeModules by lazy {
resolver.compositeNodeModules
}
@Transient @Transient
val npmProject = compilation.npmProject val npmProject = compilation.npmProject
@@ -365,13 +373,13 @@ internal class KotlinCompilationNpmResolver(
?: error("Unresolved dependent npm package: ${this@KotlinCompilationNpmResolver} -> $it") ?: error("Unresolved dependent npm package: ${this@KotlinCompilationNpmResolver} -> $it")
} }
val importedExternalGradleDependencies = fileExternalGradleDependencies.mapNotNull { val importedExternalGradleDependencies = fileExternalGradleDependencies.mapNotNull {
resolver.gradleNodeModules.get(it.dependencyName, it.dependencyVersion, it.file) gradleNodeModules.get(it.dependencyName, it.dependencyVersion, it.file)
} + fileCollectionDependencies.flatMap { dependency -> } + fileCollectionDependencies.flatMap { dependency ->
dependency.files dependency.files
// Gradle can hash with FileHasher only files and only existed files // Gradle can hash with FileHasher only files and only existed files
.filter { it.isFile } .filter { it.isFile }
.map { file -> .map { file ->
resolver.gradleNodeModules.get( gradleNodeModules.get(
file.name, file.name,
dependency.version ?: "0.0.1", dependency.version ?: "0.0.1",
file file
@@ -382,7 +390,7 @@ internal class KotlinCompilationNpmResolver(
val compositeDependencies = internalCompositeDependencies.flatMap { dependency -> val compositeDependencies = internalCompositeDependencies.flatMap { dependency ->
dependency.getPackages() dependency.getPackages()
.map { file -> .map { file ->
resolver.compositeNodeModules.get( compositeNodeModules.get(
dependency.dependency.moduleName, dependency.dependency.moduleName,
dependency.dependency.moduleVersion, dependency.dependency.moduleVersion,
file file
@@ -391,10 +399,10 @@ internal class KotlinCompilationNpmResolver(
} }
.filterNotNull() .filterNotNull()
val toolsNpmDependencies = nodeJs.taskRequirements // val toolsNpmDependencies = nodeJs.taskRequirements
.getCompilationNpmRequirements(compilation) // .getCompilationNpmRequirements(compilation)
val allNpmDependencies = externalNpmDependencies + toolsNpmDependencies val allNpmDependencies = externalNpmDependencies/* + toolsNpmDependencies*/
val packageJson = packageJson( val packageJson = packageJson(
npmProject, npmProject,