rra/ilgonmic/ic-to-compiler

[JS IR] Fix compilation in tests

[JS IR] Fix test after migrating IC to compiler

[JS IR] Fix memory leak

[JS IR] Move js ic to compiler

Merge-request: KT-MR-5673
Merged-by: Ilya Goncharov <Ilya.Goncharov@jetbrains.com>
This commit is contained in:
Ilya Goncharov
2022-02-07 11:40:16 +00:00
committed by Space
parent aadfc59d0f
commit 77b367fe96
7 changed files with 229 additions and 374 deletions
@@ -40,7 +40,7 @@ import org.jetbrains.kotlin.incremental.js.IncrementalNextRoundChecker
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer
import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCacheForModule import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCaches
import org.jetbrains.kotlin.ir.backend.js.ic.buildCacheForModuleFiles import org.jetbrains.kotlin.ir.backend.js.ic.buildCacheForModuleFiles
import org.jetbrains.kotlin.ir.backend.js.ic.loadModuleCaches import org.jetbrains.kotlin.ir.backend.js.ic.loadModuleCaches
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
@@ -196,9 +196,9 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
// TODO: Handle non-empty main call arguments // TODO: Handle non-empty main call arguments
val mainCallArguments = if (K2JsArgumentConstants.NO_CALL == arguments.main) null else emptyList<String>() val mainCallArguments = if (K2JsArgumentConstants.NO_CALL == arguments.main) null else emptyList<String>()
val icCaches = configureLibraries(arguments.cacheDirectories) val cacheDirectories = configureLibraries(arguments.cacheDirectories)
if (arguments.irBuildCache) { val icCaches = if (cacheDirectories.isNotEmpty()) {
messageCollector.report(INFO, "") messageCollector.report(INFO, "")
messageCollector.report(INFO, "Building cache:") messageCollector.report(INFO, "Building cache:")
messageCollector.report(INFO, "to: ${outputFilePath}") messageCollector.report(INFO, "to: ${outputFilePath}")
@@ -209,24 +209,22 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val updateStatus = actualizeCacheForModule( actualizeCaches(
includes, includes,
outputFilePath,
configurationJs, configurationJs,
libraries, libraries,
icCaches, cacheDirectories,
IrFactoryImplForJsIC(WholeWorldStageController()), { IrFactoryImplForJsIC(WholeWorldStageController()) },
mainCallArguments, mainCallArguments,
::buildCacheForModuleFiles ::buildCacheForModuleFiles,
) ) { updateStatus ->
if (updateStatus.upToDate) {
if (updateStatus.upToDate) { messageCollector.report(INFO, "IC per-file cache up-to-date check duration: ${System.currentTimeMillis() - start}ms")
messageCollector.report(INFO, "IC per-file cache up-to-date check duration: ${System.currentTimeMillis() - start}ms") } else {
} else { messageCollector.report(INFO, "IC per-file cache building duration: ${System.currentTimeMillis() - start}ms")
messageCollector.report(INFO, "IC per-file cache building duration: ${System.currentTimeMillis() - start}ms") }
} }
return OK } else emptyList()
}
// Run analysis if main module is sources // Run analysis if main module is sources
lateinit var sourceModule: ModulesStructure lateinit var sourceModule: ModulesStructure
@@ -31,5 +31,15 @@ data class CacheInfo(val path: String, val libPath: String, var flatHash: ULong,
val configHashULong = configHash.toULongOrNull(16) ?: 0UL val configHashULong = configHash.toULongOrNull(16) ?: 0UL
return CacheInfo(path, libPath, flatHash.toULong(16), transHash.toULong(16), configHashULong) return CacheInfo(path, libPath, flatHash.toULong(16), transHash.toULong(16), configHashULong)
} }
fun loadOrCreate(
path: String,
moduleName: String,
flatHash: ULong = 0UL,
transHash: ULong = 0UL,
configHash: ULong = 0UL
): CacheInfo {
return load(path) ?: CacheInfo(path, moduleName, flatHash, transHash, configHash)
}
} }
} }
@@ -40,7 +40,9 @@ import java.security.MessageDigest
import org.jetbrains.kotlin.backend.common.serialization.proto.IrFile as ProtoFile import org.jetbrains.kotlin.backend.common.serialization.proto.IrFile as ProtoFile
private fun KotlinLibrary.fingerprint(fileIndex: Int): Hash { private fun KotlinLibrary.fingerprint(fileIndex: Int): Hash {
return ((((types(fileIndex).md5() * 31) + signatures(fileIndex).md5()) * 31 + strings(fileIndex).md5()) * 31 + declarations(fileIndex).md5()) * 31 + bodies(fileIndex).md5() return ((((types(fileIndex).md5() * 31) + signatures(fileIndex).md5()) * 31 + strings(fileIndex).md5()) * 31 + declarations(fileIndex).md5()) * 31 + bodies(
fileIndex
).md5()
} }
private fun invalidateCacheForModule( private fun invalidateCacheForModule(
@@ -295,7 +297,7 @@ private fun createCacheConsumer(path: String): PersistentCacheConsumer {
return PersistentCacheConsumerImpl(path) return PersistentCacheConsumerImpl(path)
} }
private fun loadCacheInfo(cachePaths: Collection<String>): MutableMap<ModulePath, CacheInfo> { fun loadCacheInfo(cachePaths: Collection<String>): MutableMap<ModulePath, CacheInfo> {
val caches = cachePaths.map { CacheInfo.load(it) ?: error("Cannot load IC cache from $it") } val caches = cachePaths.map { CacheInfo.load(it) ?: error("Cannot load IC cache from $it") }
val result = mutableMapOf<ModulePath, CacheInfo>() val result = mutableMapOf<ModulePath, CacheInfo>()
return caches.associateByTo(result) { it.libPath.toCanonicalPath() } return caches.associateByTo(result) { it.libPath.toCanonicalPath() }
@@ -311,7 +313,7 @@ private fun loadLibraries(configuration: CompilerConfiguration, dependencies: Co
return allResolvedDependencies.getFullList().associateBy { it.libraryFile.path.toCanonicalPath() } return allResolvedDependencies.getFullList().associateBy { it.libraryFile.path.toCanonicalPath() }
} }
private fun String.toCanonicalPath(): String = File(this).canonicalPath fun String.toCanonicalPath(): String = File(this).canonicalPath
typealias ModuleName = String typealias ModuleName = String
typealias ModulePath = String typealias ModulePath = String
@@ -408,35 +410,101 @@ enum class CacheUpdateStatus(val upToDate: Boolean) {
} }
fun actualizeCaches(
includes: String,
compilerConfiguration: CompilerConfiguration,
dependencies: Collection<ModulePath>,
icCachePaths: Collection<String>,
irFactory: () -> IrFactory,
mainArguments: List<String>?,
executor: CacheExecutor,
callback: (CacheUpdateStatus) -> Unit
): List<String> {
val (libraries, dependencyGraph, configMD5) = CacheConfiguration(dependencies, compilerConfiguration)
val cacheMap = libraries.values.zip(icCachePaths).toMap()
val icCacheMap: MutableMap<ModulePath, CacheInfo> = mutableMapOf<ModulePath, CacheInfo>()
val resultCaches = mutableListOf<String>()
val visitedLibraries = mutableSetOf<KotlinLibrary>()
fun visitDependency(library: KotlinLibrary) {
if (library in visitedLibraries) return
visitedLibraries.add(library)
val libraryDeps = dependencyGraph[library] ?: error("Unknown library ${library.libraryName}")
libraryDeps.forEach { visitDependency(it) }
val cachePath = cacheMap[library] ?: error("Unknown cache for library ${library.libraryName}")
resultCaches.add(cachePath)
val moduleName = library.libraryFile.path.toCanonicalPath()
val cacheInfo = CacheInfo.loadOrCreate(cachePath, moduleName, configHash = configMD5)
icCacheMap[moduleName] = cacheInfo
val updateStatus = actualizeCacheForModule(
moduleName = moduleName,
cachePath = cachePath,
compilerConfiguration = compilerConfiguration,
configMD5 = configMD5,
libraries = libraries,
dependencyGraph = dependencyGraph,
icCacheMap = icCacheMap,
irFactory = irFactory(),
mainArguments = mainArguments,
executor = executor
)
callback(updateStatus)
}
val canonicalIncludes = includes.toCanonicalPath()
val mainLibrary = libraries[canonicalIncludes] ?: error("Main library not found in libraries: $canonicalIncludes")
visitDependency(mainLibrary)
return resultCaches
}
class CacheConfiguration(
private val dependencies: Collection<ModulePath>,
val compilerConfiguration: CompilerConfiguration
) {
val libraries: Map<ModulePath, KotlinLibrary> = loadLibraries(compilerConfiguration, dependencies)
val dependencyGraph: Map<KotlinLibrary, List<KotlinLibrary>>
get() {
val nameToKotlinLibrary: Map<ModuleName, KotlinLibrary> = libraries.values.associateBy { it.moduleName }
return libraries.values.associateWith {
it.manifestProperties.propertyList(KLIB_PROPERTY_DEPENDS, escapeInQuotes = true).map { depName ->
nameToKotlinLibrary[depName] ?: error("No Library found for $depName")
}
}
}
val configMD5
get() = compilerConfiguration.calcMD5()
operator fun component1() = libraries
operator fun component2() = dependencyGraph
operator fun component3() = configMD5
}
// Returns true if caches up-to-date // Returns true if caches up-to-date
fun actualizeCacheForModule( fun actualizeCacheForModule(
moduleName: String, moduleName: String,
cachePath: String, cachePath: String,
compilerConfiguration: CompilerConfiguration, compilerConfiguration: CompilerConfiguration,
dependencies: Collection<ModulePath>, configMD5: ULong,
icCachePaths: Collection<String>, libraries: Map<ModulePath, KotlinLibrary>,
dependencyGraph: Map<KotlinLibrary, List<KotlinLibrary>>,
icCacheMap: Map<ModulePath, CacheInfo>,
irFactory: IrFactory, irFactory: IrFactory,
mainArguments: List<String>?, mainArguments: List<String>?,
executor: CacheExecutor executor: CacheExecutor
): CacheUpdateStatus { ): CacheUpdateStatus {
val configMD5 = compilerConfiguration.calcMD5() val cacheInfo = icCacheMap[moduleName] ?: error("Cache for $moduleName not found")
val modulePath = moduleName.toCanonicalPath()
val cacheInfo = CacheInfo.load(cachePath) ?: CacheInfo(cachePath, modulePath, 0UL, 0UL, configMD5)
val icCacheMap: Map<ModulePath, CacheInfo> = loadCacheInfo(icCachePaths).also {
it[modulePath] = cacheInfo
}
val libraries: Map<ModulePath, KotlinLibrary> = loadLibraries(compilerConfiguration, dependencies)
val nameToKotlinLibrary: Map<ModuleName, KotlinLibrary> = libraries.values.associateBy { it.moduleName }
val dependencyGraph = libraries.values.associateWith {
it.manifestProperties.propertyList(KLIB_PROPERTY_DEPENDS, escapeInQuotes = true).map { depName ->
nameToKotlinLibrary[depName] ?: error("No Library found for $depName")
}
}
val configUpdated = configMD5 != cacheInfo.configHash val configUpdated = configMD5 != cacheInfo.configHash
cacheInfo.configHash = configMD5 cacheInfo.configHash = configMD5
if (checkLibrariesHash(libraries, dependencyGraph, icCacheMap, modulePath) && !configUpdated) { if (checkLibrariesHash(libraries, dependencyGraph, icCacheMap, moduleName) && !configUpdated) {
return CacheUpdateStatus.FAST_PATH // up-to-date return CacheUpdateStatus.FAST_PATH // up-to-date
} }
@@ -444,7 +512,7 @@ fun actualizeCacheForModule(
libraries[lib.toCanonicalPath()]!! to createCacheProvider(cache.path) libraries[lib.toCanonicalPath()]!! to createCacheProvider(cache.path)
}.toMap() }.toMap()
val currentModule = libraries[moduleName.toCanonicalPath()] ?: error("No loaded library found for path $moduleName") val currentModule = libraries[moduleName] ?: error("No loaded library found for path $moduleName")
val persistentCacheConsumer = createCacheConsumer(cachePath) val persistentCacheConsumer = createCacheConsumer(cachePath)
return actualizeCacheForModule( return actualizeCacheForModule(
@@ -17,17 +17,15 @@ import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.generateKLib import org.jetbrains.kotlin.ir.backend.js.generateKLib
import org.jetbrains.kotlin.ir.backend.js.ic.CacheUpdateStatus import org.jetbrains.kotlin.ir.backend.js.ic.*
import org.jetbrains.kotlin.ir.backend.js.ic.PersistentCacheConsumer
import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCacheForModule
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
import org.jetbrains.kotlin.ir.backend.js.prepareAnalyzedSourceModule import org.jetbrains.kotlin.ir.backend.js.prepareAnalyzedSourceModule
import org.jetbrains.kotlin.ir.declarations.IrFactory
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
import org.jetbrains.kotlin.test.util.JUnit4Assertions import org.jetbrains.kotlin.test.util.JUnit4Assertions
import java.io.File import java.io.File
import kotlin.io.path.createTempDirectory import kotlin.io.path.createTempDirectory
@@ -58,7 +56,8 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
cacheConsumer: PersistentCacheConsumer, cacheConsumer: PersistentCacheConsumer,
exportedDeclarations: Set<FqName>, exportedDeclarations: Set<FqName>,
mainArguments: List<String>?, mainArguments: List<String>?,
) { } ) {
}
private fun initializeStdlibCache() { private fun initializeStdlibCache() {
if (stdlibCacheDir != null) return if (stdlibCacheDir != null) return
@@ -67,7 +66,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
val configuration = createConfiguration("stdlib") val configuration = createConfiguration("stdlib")
actualizeCacheForModule( actualizeCacheForModuleWithPrepare(
stdlibKlibPath, stdlibKlibPath,
cacheDir.canonicalPath, cacheDir.canonicalPath,
configuration, configuration,
@@ -158,7 +157,17 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
val moduleCacheDir = resolveModuleCache(module, buildDir) val moduleCacheDir = resolveModuleCache(module, buildDir)
buildCachesAndCheck(moduleStep, configuration, moduleSourceDir, outputKlibFile, moduleCacheDir, dependencies, icCaches, dirtyFiles, deletedFiles) buildCachesAndCheck(
moduleStep,
configuration,
moduleSourceDir,
outputKlibFile,
moduleCacheDir,
dependencies,
icCaches,
dirtyFiles,
deletedFiles
)
} }
} }
} }
@@ -212,7 +221,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
dependencies.mapTo(dependenciesPaths) { it.canonicalPath } dependencies.mapTo(dependenciesPaths) { it.canonicalPath }
dependenciesPaths.add(moduleKlibFile.canonicalPath) dependenciesPaths.add(moduleKlibFile.canonicalPath)
val updateStatus = actualizeCacheForModule( val updateStatus = actualizeCacheForModuleWithPrepare(
moduleKlibFile.canonicalPath, moduleKlibFile.canonicalPath,
moduleCacheDir.canonicalPath, moduleCacheDir.canonicalPath,
configuration, configuration,
@@ -235,6 +244,41 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
} }
} }
private fun actualizeCacheForModuleWithPrepare(
moduleName: String,
cachePath: String,
compilerConfiguration: CompilerConfiguration,
dependencies: Collection<ModulePath>,
icCachePaths: Collection<String>,
irFactory: IrFactory,
mainArguments: List<String>?,
executor: CacheExecutor
): CacheUpdateStatus {
val (libraries, dependencyGraph, configMD5) = CacheConfiguration(dependencies, compilerConfiguration)
val modulePath = moduleName.toCanonicalPath()
val icCacheMap: Map<ModulePath, CacheInfo> = loadCacheInfo(icCachePaths).also {
it[modulePath] = CacheInfo.loadOrCreate(
cachePath,
moduleName,
configHash = configMD5
)
}
return actualizeCacheForModule(
moduleName = modulePath,
cachePath = cachePath,
compilerConfiguration = compilerConfiguration,
configMD5 = configMD5,
libraries = libraries,
dependencyGraph = dependencyGraph,
icCacheMap = icCacheMap,
irFactory = irFactory,
mainArguments = mainArguments,
executor = executor
)
}
private fun KotlinCoreEnvironment.createPsiFile(file: File): KtFile { private fun KotlinCoreEnvironment.createPsiFile(file: File): KtFile {
val psiManager = PsiManager.getInstance(project) val psiManager = PsiManager.getInstance(project)
val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem
@@ -25,7 +25,6 @@ import org.jetbrains.kotlin.gradle.util.normalizePath
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.DisabledIf import org.junit.jupiter.api.condition.DisabledIf
import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Paths import java.nio.file.Paths
import java.util.zip.ZipFile import java.util.zip.ZipFile
@@ -178,7 +177,7 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
buildGradleKts.modify(::transformBuildScriptWithPluginsDsl) buildGradleKts.modify(::transformBuildScriptWithPluginsDsl)
build("compileDevelopmentExecutableKotlinJs") { build("compileDevelopmentExecutableKotlinJs") {
val cacheDir = projectPath.resolve("app/build/klib/cache/lib/unspecified/") val cacheDir = projectPath.resolve("app/build/klib/cache/lib/")
.toFile() .toFile()
assertTrue("Lib cache size should be 2") { assertTrue("Lib cache size should be 2") {
cacheDir cacheDir
@@ -186,33 +185,30 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
?.size == 2 ?.size == 2
} }
var lib: Boolean = false var lib = false
var libOther: Boolean = false var libOther = false
cacheDir.listFiles()!! cacheDir.listFiles()!!
.forEach { .forEach {
it.listFiles()!! it.listFiles()!!
.single() .filter { it.isFile }
.let { .forEach {
it.listFiles()!! val text = it.readText()
.filter { it.isFile } if (text.contains("<kotlin-js-ir-ic-multiple-artifacts:lib>")) {
.forEach { if (lib) {
val text = it.readText() error("lib should be only once in cache")
if (text.contains("<kotlin-js-ir-ic-multiple-artifacts:lib>")) {
if (lib) {
error("lib should be only once in cache")
}
lib = true
}
if (text.contains("<kotlin-js-ir-ic-multiple-artifacts:lib_other>")) {
if (libOther) {
error("libOther should be only once in cache")
}
libOther = true
}
} }
lib = true
}
if (text.contains("<kotlin-js-ir-ic-multiple-artifacts:lib_other>")) {
if (libOther) {
error("libOther should be only once in cache")
}
libOther = true
}
} }
} }
assertTrue("lib and libOther should be once in cache") { assertTrue("lib and libOther should be once in cache") {
@@ -5,47 +5,27 @@
package org.jetbrains.kotlin.gradle.targets.js.ir package org.jetbrains.kotlin.gradle.targets.js.ir
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree import org.gradle.api.file.FileTree
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.work.InputChanges import org.gradle.internal.hash.FileHasher
import org.gradle.workers.WorkerExecutor import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.compilerRunner.ArgumentUtils import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptionsImpl import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptionsImpl
import org.jetbrains.kotlin.gradle.dsl.copyFreeCompilerArgsToArgs
import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.report.ReportingSettings
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.DEVELOPMENT import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.DEVELOPMENT
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.PRODUCTION import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.PRODUCTION
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.SourceRoots
import org.jetbrains.kotlin.gradle.tasks.TaskOutputsBackup
import org.jetbrains.kotlin.gradle.utils.getAllDependencies
import org.jetbrains.kotlin.gradle.utils.getCacheDirectory
import org.jetbrains.kotlin.gradle.utils.getDependenciesCacheDirectories
import org.jetbrains.kotlin.gradle.utils.getValue import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.library.resolveSingleFileKlib
import org.jetbrains.kotlin.library.uniqueName
import org.jetbrains.kotlin.library.unresolvedDependencies
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
import org.jetbrains.kotlin.statistics.metrics.StringMetrics import org.jetbrains.kotlin.statistics.metrics.StringMetrics
import java.io.File import java.io.File
@@ -77,10 +57,18 @@ abstract class KotlinJsIrLink @Inject constructor(
@get:Internal @get:Internal
internal lateinit var compilation: KotlinCompilationData<*> internal lateinit var compilation: KotlinCompilationData<*>
private val platformType by project.provider {
compilation.platformType
}
@Transient @Transient
@get:Internal @get:Internal
internal val propertiesProvider = PropertiesProvider(project) internal val propertiesProvider = PropertiesProvider(project)
@get:Inject
open val fileHasher: FileHasher
get() = throw UnsupportedOperationException()
@get:Input @get:Input
internal val incrementalJsIr: Boolean = propertiesProvider.incrementalJsIr internal val incrementalJsIr: Boolean = propertiesProvider.incrementalJsIr
@@ -117,12 +105,13 @@ abstract class KotlinJsIrLink @Inject constructor(
return !entryModule.get().asFile.exists() return !entryModule.get().asFile.exists()
} }
override fun callCompilerAsync( @get:Internal
args: K2JSCompilerArguments, val rootCacheDirectory by lazy {
sourceRoots: SourceRoots, buildDir.resolve("klib/cache")
inputChanges: InputChanges, }
taskOutputsBackup: TaskOutputsBackup?
) { override fun processArgs(args: K2JSCompilerArguments) {
super.processArgs(args)
KotlinBuildStatsService.applyIfInitialised { KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_IR_INCREMENTAL, incrementalJsIr) it.report(BooleanMetrics.JS_IR_INCREMENTAL, incrementalJsIr)
val newArgs = K2JSCompilerArguments() val newArgs = K2JSCompilerArguments()
@@ -136,55 +125,31 @@ abstract class KotlinJsIrLink @Inject constructor(
) )
} }
if (incrementalJsIr && mode == DEVELOPMENT) { if (incrementalJsIr && mode == DEVELOPMENT) {
val visitedCompilations = mutableSetOf<KotlinCompilation<*>>() args.cacheDirectories = args.libraries?.splitByPathSeparator()
?.map {
val cacheBuilder = CacheBuilder( val file = File(it)
buildDir, rootCacheDirectory
kotlinOptions, .resolve(file.nameWithoutExtension)
libraryFilter, .also {
compilerRunner.get(), it.mkdirs()
{ createCompilerArgs() }, }
{ objects.fileCollection() }, .resolve(fileHasher.hash(file).toString())
defaultCompilerClasspath, }
logger, ?.plus(rootCacheDirectory.resolve(entryModule.get().asFile.name))
reportingSettings() ?.let {
) if (it.isNotEmpty())
val cacheArgs = visitCompilation( it.joinToString(File.pathSeparator)
compilation as KotlinCompilation<*>, else
cacheBuilder, null
visitedCompilations, }
)
args.cacheDirectories = cacheArgs.joinToString(File.pathSeparator) {
it.normalize().absolutePath
}
} }
super.callCompilerAsync(args, sourceRoots, inputChanges, taskOutputsBackup)
} }
private fun visitCompilation( private fun String.splitByPathSeparator(): List<String> {
compilation: KotlinCompilation<*>, return this.split(File.pathSeparator.toRegex())
cacheBuilder: CacheBuilder, .dropLastWhile { it.isEmpty() }
visitedCompilations: MutableSet<KotlinCompilation<*>>, .toTypedArray()
): List<File> { .filterNot { it.isEmpty() }
if (compilation in visitedCompilations) return emptyList()
visitedCompilations.add(compilation)
val associatedCaches = compilation.associateWith
.flatMap {
visitCompilation(
it,
cacheBuilder,
visitedCompilations,
)
}
return cacheBuilder
.buildCompilerArgs(
project.configurations.getByName(compilation.compileDependencyConfigurationName),
compilation.output.classesDirs,
associatedCaches
)
} }
override fun setupCompilerArgs(args: K2JSCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) { override fun setupCompilerArgs(args: K2JSCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
@@ -204,10 +169,6 @@ abstract class KotlinJsIrLink @Inject constructor(
super.setupCompilerArgs(args, defaultsOnly, ignoreClasspathResolutionErrors) super.setupCompilerArgs(args, defaultsOnly, ignoreClasspathResolutionErrors)
} }
private val platformType by project.provider {
compilation.platformType
}
private fun KotlinJsOptions.configureOptions(vararg additionalCompilerArgs: String) { private fun KotlinJsOptions.configureOptions(vararg additionalCompilerArgs: String) {
freeCompilerArgs += additionalCompilerArgs.toList() + freeCompilerArgs += additionalCompilerArgs.toList() +
PRODUCE_JS + PRODUCE_JS +
@@ -217,233 +178,4 @@ abstract class KotlinJsIrLink @Inject constructor(
freeCompilerArgs += WASM_BACKEND freeCompilerArgs += WASM_BACKEND
} }
} }
}
internal class CacheBuilder(
private val buildDir: File,
private val kotlinOptions: KotlinJsOptions,
private val libraryFilter: (File) -> Boolean,
private val compilerRunner: GradleCompilerRunner,
private val compilerArgsFactory: () -> K2JSCompilerArguments,
private val objectFilesFactory: () -> FileCollection,
private val computedCompilerClasspath: FileCollection,
private val logger: Logger,
private val reportingSettings: ReportingSettings
) {
val rootCacheDirectory by lazy {
buildDir.resolve("klib/cache")
}
private val visitedDependencies = mutableSetOf<ResolvedDependency>()
private val visitedFiles = mutableSetOf<File>()
private val visitedCacheDirectories = mutableSetOf<File>()
private val objectFiles
get() = objectFilesFactory()
fun buildCompilerArgs(
compileClasspath: Configuration,
additionalForResolve: FileCollection?,
associatedCaches: List<File>
): List<File> {
val allCacheDirectories = mutableListOf<File>()
val visitedDependenciesForCache = mutableSetOf<File>()
compileClasspath.resolvedConfiguration.firstLevelModuleDependencies
.forEach { dependency ->
ensureDependencyCached(
dependency
).forEach {
if (it !in visitedDependenciesForCache) {
visitedDependenciesForCache.add(it)
allCacheDirectories.add(it)
}
}
}
additionalForResolve?.files?.forEach { file ->
val cacheDirectory = rootCacheDirectory.resolve(file.name)
cacheDirectory.mkdirs()
runCompiler(
file,
compileClasspath.files,
cacheDirectory,
(allCacheDirectories + associatedCaches).distinct()
)
allCacheDirectories.add(cacheDirectory)
}
return associatedCaches + allCacheDirectories
.filter { it !in visitedCacheDirectories }
.also { visitedCacheDirectories.addAll(it) }
}
private fun ensureDependencyCached(
dependency: ResolvedDependency
): List<File> {
if (dependency in visitedDependencies) {
return dependency.moduleArtifacts
.filter { libraryFilter(it.file) }
.map {
getCacheDirectory(rootCacheDirectory, dependency, it, { libraryFilter(it.file) })
}
}
visitedDependencies.add(dependency)
val depCacheDirs = dependency.children
.flatMap { ensureDependencyCached(it) }
.distinct()
val artifactsToAddToCache = dependency.moduleArtifacts
.filter { libraryFilter(it.file) }
if (artifactsToAddToCache.isEmpty()) return depCacheDirs
val dependenciesCacheDirectories = getDependenciesCacheDirectories(
rootCacheDirectory,
dependency,
libraryFilter = { libraryFilter(it.file) },
considerArtifact = true
) ?: return depCacheDirs
val nameMap: MutableMap<String, ResolvedArtifact> = mutableMapOf()
val depsMap: MutableMap<ResolvedArtifact, MutableList<ResolvedArtifact>> = mutableMapOf()
val cacheMap: MutableMap<ResolvedArtifact, File> = mutableMapOf()
var newOrderArtifactsToAddToCache = mutableListOf<ResolvedArtifact>()
if (artifactsToAddToCache.size > 1) {
artifactsToAddToCache.forEach {
val klib = resolveSingleFileKlib(org.jetbrains.kotlin.konan.file.File(it.file.absolutePath))
nameMap[klib.uniqueName] = it
}
artifactsToAddToCache.forEach { artifact ->
val klib = resolveSingleFileKlib(org.jetbrains.kotlin.konan.file.File(artifact.file.absolutePath))
klib.unresolvedDependencies
.forEach { depLib ->
val depName = depLib.path.substringAfterLast("/")
nameMap[depName]?.let {
val depsSet = depsMap.getOrPut(artifact) {
mutableListOf()
}
depsSet.add(it)
}
}
}
fun traverseDependency(library: ResolvedArtifact) {
depsMap[library]?.let {
it.forEach {
traverseDependency(it)
}
}
if (library !in newOrderArtifactsToAddToCache) {
newOrderArtifactsToAddToCache.add(library)
}
}
depsMap.forEach { key, deps ->
traverseDependency(key)
}
} else {
newOrderArtifactsToAddToCache = artifactsToAddToCache.toMutableList()
}
val additionalCacheDirs = mutableListOf<File>()
for (library in newOrderArtifactsToAddToCache) {
val cacheDirectory = getCacheDirectory(rootCacheDirectory, dependency, library, { libraryFilter(it.file) }).also {
additionalCacheDirs.add(it)
}
cacheDirectory.mkdirs()
cacheMap[library] = cacheDirectory
val additionalDependencies = depsMap[library] ?: emptyList()
val additionalCacheDirectories = if (additionalDependencies.isNotEmpty()) {
additionalDependencies.map { cacheMap.getValue(it) }
} else emptyList()
runCompiler(
library.file,
(getAllDependencies(dependency)
.flatMap { it.moduleArtifacts } + additionalDependencies)
.map { it.file },
cacheDirectory,
dependenciesCacheDirectories + additionalCacheDirectories
)
}
return depCacheDirs + additionalCacheDirs
}
fun runCompiler(
file: File,
dependencies: Collection<File>,
cacheDirectory: File,
dependenciesCacheDirectories: Collection<File>
) {
if (file in visitedFiles) return
val compilerArgs = compilerArgsFactory()
kotlinOptions.copyFreeCompilerArgsToArgs(compilerArgs)
var prevIndex: Int? = null
compilerArgs.freeArgs = compilerArgs.freeArgs
.filterIndexed { index, arg ->
!listOf("-source-map-base-dirs", "-source-map-prefix").any {
if (prevIndex != null) {
prevIndex = null
return@any true
}
if (arg.startsWith(it)) {
prevIndex = index
return@any true
}
false
}
}
compilerArgs.freeArgs = compilerArgs.freeArgs
.filterNot { arg ->
IGNORED_ARGS.any {
arg.startsWith(it)
}
}
visitedFiles.add(file)
compilerArgs.includes = file.normalize().absolutePath
compilerArgs.outputFile = cacheDirectory.normalize().absolutePath
if (dependenciesCacheDirectories.isNotEmpty()) {
compilerArgs.cacheDirectories = dependenciesCacheDirectories.joinToString(File.pathSeparator)
}
compilerArgs.irBuildCache = true
compilerArgs.libraries = dependencies
.filter { it.exists() && libraryFilter(it) }
.distinct()
.filterNot { it == file }
.joinToString(File.pathSeparator) { it.normalize().absolutePath }
val messageCollector = GradlePrintingMessageCollector(logger, false)
val outputItemCollector = OutputItemsCollectorImpl()
val environment = GradleCompilerEnvironment(
computedCompilerClasspath,
messageCollector,
outputItemCollector,
outputFiles = objectFiles.files.toList(),
reportingSettings = reportingSettings
)
compilerRunner
.runJsCompilerAsync(
emptyList(),
emptyList(),
compilerArgs,
environment,
null
)?.await()
}
companion object {
private val IGNORED_ARGS = listOf(
ENTRY_IR_MODULE,
PRODUCE_JS,
PRODUCE_UNZIPPED_KLIB,
ENABLE_DCE,
GENERATE_D_TS
)
}
} }
@@ -1094,6 +1094,12 @@ abstract class Kotlin2JsCompile @Inject constructor(
override val incrementalProps: List<FileCollection> override val incrementalProps: List<FileCollection>
get() = super.incrementalProps + listOf(friendDependencies) get() = super.incrementalProps + listOf(friendDependencies)
open fun processArgs(
args: K2JSCompilerArguments
) {
}
override fun callCompilerAsync( override fun callCompilerAsync(
args: K2JSCompilerArguments, args: K2JSCompilerArguments,
sourceRoots: SourceRoots, sourceRoots: SourceRoots,
@@ -1147,6 +1153,7 @@ abstract class Kotlin2JsCompile @Inject constructor(
reportingSettings = reportingSettings(), reportingSettings = reportingSettings(),
incrementalCompilationEnvironment = icEnv incrementalCompilationEnvironment = icEnv
) )
processArgs(args)
compilerRunner.runJsCompilerAsync( compilerRunner.runJsCompilerAsync(
sourceRoots.kotlinSourceFiles.files.toList(), sourceRoots.kotlinSourceFiles.files.toList(),
commonSourceSet.toList(), commonSourceSet.toList(),