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.ir.backend.js.*
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.loadModuleCaches
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
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, "Building cache:")
messageCollector.report(INFO, "to: ${outputFilePath}")
@@ -209,24 +209,22 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
val start = System.currentTimeMillis()
val updateStatus = actualizeCacheForModule(
actualizeCaches(
includes,
outputFilePath,
configurationJs,
libraries,
icCaches,
IrFactoryImplForJsIC(WholeWorldStageController()),
cacheDirectories,
{ IrFactoryImplForJsIC(WholeWorldStageController()) },
mainCallArguments,
::buildCacheForModuleFiles
)
if (updateStatus.upToDate) {
messageCollector.report(INFO, "IC per-file cache up-to-date check duration: ${System.currentTimeMillis() - start}ms")
} else {
messageCollector.report(INFO, "IC per-file cache building duration: ${System.currentTimeMillis() - start}ms")
::buildCacheForModuleFiles,
) { updateStatus ->
if (updateStatus.upToDate) {
messageCollector.report(INFO, "IC per-file cache up-to-date check duration: ${System.currentTimeMillis() - start}ms")
} else {
messageCollector.report(INFO, "IC per-file cache building duration: ${System.currentTimeMillis() - start}ms")
}
}
return OK
}
} else emptyList()
// Run analysis if main module is sources
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
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
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(
@@ -295,7 +297,7 @@ private fun createCacheConsumer(path: String): PersistentCacheConsumer {
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 result = mutableMapOf<ModulePath, CacheInfo>()
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() }
}
private fun String.toCanonicalPath(): String = File(this).canonicalPath
fun String.toCanonicalPath(): String = File(this).canonicalPath
typealias ModuleName = 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
fun actualizeCacheForModule(
moduleName: String,
cachePath: String,
compilerConfiguration: CompilerConfiguration,
dependencies: Collection<ModulePath>,
icCachePaths: Collection<String>,
configMD5: ULong,
libraries: Map<ModulePath, KotlinLibrary>,
dependencyGraph: Map<KotlinLibrary, List<KotlinLibrary>>,
icCacheMap: Map<ModulePath, CacheInfo>,
irFactory: IrFactory,
mainArguments: List<String>?,
executor: CacheExecutor
): CacheUpdateStatus {
val configMD5 = compilerConfiguration.calcMD5()
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 cacheInfo = icCacheMap[moduleName] ?: error("Cache for $moduleName not found")
val configUpdated = configMD5 != cacheInfo.configHash
cacheInfo.configHash = configMD5
if (checkLibrariesHash(libraries, dependencyGraph, icCacheMap, modulePath) && !configUpdated) {
if (checkLibrariesHash(libraries, dependencyGraph, icCacheMap, moduleName) && !configUpdated) {
return CacheUpdateStatus.FAST_PATH // up-to-date
}
@@ -444,7 +512,7 @@ fun actualizeCacheForModule(
libraries[lib.toCanonicalPath()]!! to createCacheProvider(cache.path)
}.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)
return actualizeCacheForModule(
@@ -17,17 +17,15 @@ import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
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.PersistentCacheConsumer
import org.jetbrains.kotlin.ir.backend.js.ic.actualizeCacheForModule
import org.jetbrains.kotlin.ir.backend.js.ic.*
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
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.impl.IrFactoryImpl
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
import org.jetbrains.kotlin.test.util.JUnit4Assertions
import java.io.File
import kotlin.io.path.createTempDirectory
@@ -58,7 +56,8 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
cacheConsumer: PersistentCacheConsumer,
exportedDeclarations: Set<FqName>,
mainArguments: List<String>?,
) { }
) {
}
private fun initializeStdlibCache() {
if (stdlibCacheDir != null) return
@@ -67,7 +66,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
val configuration = createConfiguration("stdlib")
actualizeCacheForModule(
actualizeCacheForModuleWithPrepare(
stdlibKlibPath,
cacheDir.canonicalPath,
configuration,
@@ -158,7 +157,17 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
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 }
dependenciesPaths.add(moduleKlibFile.canonicalPath)
val updateStatus = actualizeCacheForModule(
val updateStatus = actualizeCacheForModuleWithPrepare(
moduleKlibFile.canonicalPath,
moduleCacheDir.canonicalPath,
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 {
val psiManager = PsiManager.getInstance(project)
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.DisplayName
import org.junit.jupiter.api.condition.DisabledIf
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.util.zip.ZipFile
@@ -178,7 +177,7 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
buildGradleKts.modify(::transformBuildScriptWithPluginsDsl)
build("compileDevelopmentExecutableKotlinJs") {
val cacheDir = projectPath.resolve("app/build/klib/cache/lib/unspecified/")
val cacheDir = projectPath.resolve("app/build/klib/cache/lib/")
.toFile()
assertTrue("Lib cache size should be 2") {
cacheDir
@@ -186,33 +185,30 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
?.size == 2
}
var lib: Boolean = false
var libOther: Boolean = false
var lib = false
var libOther = false
cacheDir.listFiles()!!
.forEach {
it.listFiles()!!
.single()
.let {
it.listFiles()!!
.filter { it.isFile }
.forEach {
val text = it.readText()
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
}
.filter { it.isFile }
.forEach {
val text = it.readText()
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
}
}
}
assertTrue("lib and libOther should be once in cache") {
@@ -5,47 +5,27 @@
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.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.*
import org.gradle.work.InputChanges
import org.gradle.internal.hash.FileHasher
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
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.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.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
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.DEVELOPMENT
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.PRODUCTION
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.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.StringMetrics
import java.io.File
@@ -77,10 +57,18 @@ abstract class KotlinJsIrLink @Inject constructor(
@get:Internal
internal lateinit var compilation: KotlinCompilationData<*>
private val platformType by project.provider {
compilation.platformType
}
@Transient
@get:Internal
internal val propertiesProvider = PropertiesProvider(project)
@get:Inject
open val fileHasher: FileHasher
get() = throw UnsupportedOperationException()
@get:Input
internal val incrementalJsIr: Boolean = propertiesProvider.incrementalJsIr
@@ -117,12 +105,13 @@ abstract class KotlinJsIrLink @Inject constructor(
return !entryModule.get().asFile.exists()
}
override fun callCompilerAsync(
args: K2JSCompilerArguments,
sourceRoots: SourceRoots,
inputChanges: InputChanges,
taskOutputsBackup: TaskOutputsBackup?
) {
@get:Internal
val rootCacheDirectory by lazy {
buildDir.resolve("klib/cache")
}
override fun processArgs(args: K2JSCompilerArguments) {
super.processArgs(args)
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_IR_INCREMENTAL, incrementalJsIr)
val newArgs = K2JSCompilerArguments()
@@ -136,55 +125,31 @@ abstract class KotlinJsIrLink @Inject constructor(
)
}
if (incrementalJsIr && mode == DEVELOPMENT) {
val visitedCompilations = mutableSetOf<KotlinCompilation<*>>()
val cacheBuilder = CacheBuilder(
buildDir,
kotlinOptions,
libraryFilter,
compilerRunner.get(),
{ createCompilerArgs() },
{ objects.fileCollection() },
defaultCompilerClasspath,
logger,
reportingSettings()
)
val cacheArgs = visitCompilation(
compilation as KotlinCompilation<*>,
cacheBuilder,
visitedCompilations,
)
args.cacheDirectories = cacheArgs.joinToString(File.pathSeparator) {
it.normalize().absolutePath
}
args.cacheDirectories = args.libraries?.splitByPathSeparator()
?.map {
val file = File(it)
rootCacheDirectory
.resolve(file.nameWithoutExtension)
.also {
it.mkdirs()
}
.resolve(fileHasher.hash(file).toString())
}
?.plus(rootCacheDirectory.resolve(entryModule.get().asFile.name))
?.let {
if (it.isNotEmpty())
it.joinToString(File.pathSeparator)
else
null
}
}
super.callCompilerAsync(args, sourceRoots, inputChanges, taskOutputsBackup)
}
private fun visitCompilation(
compilation: KotlinCompilation<*>,
cacheBuilder: CacheBuilder,
visitedCompilations: MutableSet<KotlinCompilation<*>>,
): List<File> {
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
)
private fun String.splitByPathSeparator(): List<String> {
return this.split(File.pathSeparator.toRegex())
.dropLastWhile { it.isEmpty() }
.toTypedArray()
.filterNot { it.isEmpty() }
}
override fun setupCompilerArgs(args: K2JSCompilerArguments, defaultsOnly: Boolean, ignoreClasspathResolutionErrors: Boolean) {
@@ -204,10 +169,6 @@ abstract class KotlinJsIrLink @Inject constructor(
super.setupCompilerArgs(args, defaultsOnly, ignoreClasspathResolutionErrors)
}
private val platformType by project.provider {
compilation.platformType
}
private fun KotlinJsOptions.configureOptions(vararg additionalCompilerArgs: String) {
freeCompilerArgs += additionalCompilerArgs.toList() +
PRODUCE_JS +
@@ -217,233 +178,4 @@ abstract class KotlinJsIrLink @Inject constructor(
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>
get() = super.incrementalProps + listOf(friendDependencies)
open fun processArgs(
args: K2JSCompilerArguments
) {
}
override fun callCompilerAsync(
args: K2JSCompilerArguments,
sourceRoots: SourceRoots,
@@ -1147,6 +1153,7 @@ abstract class Kotlin2JsCompile @Inject constructor(
reportingSettings = reportingSettings(),
incrementalCompilationEnvironment = icEnv
)
processArgs(args)
compilerRunner.runJsCompilerAsync(
sourceRoots.kotlinSourceFiles.files.toList(),
commonSourceSet.toList(),