[PL][tests] K/JS: Run compiler through CLI interface
This commit is contained in:
committed by
Space Team
parent
19c6208cc0
commit
715aba3d18
@@ -21,7 +21,7 @@ object PartialLinkageTestUtils {
|
|||||||
val testModeConstructorParameters: Map<String, String>
|
val testModeConstructorParameters: Map<String, String>
|
||||||
|
|
||||||
// Customize the source code of a module before compiling it to a KLIB.
|
// Customize the source code of a module before compiling it to a KLIB.
|
||||||
fun customizeModuleSources(moduleName: String, moduleSourceDir: File) = Unit
|
fun customizeModuleSources(moduleName: String, moduleSourceDir: File)
|
||||||
|
|
||||||
// Build a KLIB from a module.
|
// Build a KLIB from a module.
|
||||||
fun buildKlib(moduleName: String, buildDirs: ModuleBuildDirs, dependencies: Dependencies, klibFile: File)
|
fun buildKlib(moduleName: String, buildDirs: ModuleBuildDirs, dependencies: Dependencies, klibFile: File)
|
||||||
@@ -93,6 +93,9 @@ object PartialLinkageTestUtils {
|
|||||||
// Populate the source dir with *.kt files.
|
// Populate the source dir with *.kt files.
|
||||||
copySources(from = moduleTestDir, to = moduleBuildDirs.sourceDir)
|
copySources(from = moduleTestDir, to = moduleBuildDirs.sourceDir)
|
||||||
|
|
||||||
|
// Customize the source dir if necessary.
|
||||||
|
customizeModuleSources(moduleName, moduleBuildDirs.sourceDir)
|
||||||
|
|
||||||
// Include PL utils into the main module.
|
// Include PL utils into the main module.
|
||||||
if (moduleName == MAIN_MODULE_NAME) {
|
if (moduleName == MAIN_MODULE_NAME) {
|
||||||
val utilsDir = testDir.parentFile.resolve(PL_UTILS_DIR)
|
val utilsDir = testDir.parentFile.resolve(PL_UTILS_DIR)
|
||||||
@@ -110,8 +113,6 @@ object PartialLinkageTestUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customizeModuleSources(moduleName, moduleBuildDirs.sourceDir)
|
|
||||||
|
|
||||||
moduleBuildDirs.outputDir.apply { mkdirs() }
|
moduleBuildDirs.outputDir.apply { mkdirs() }
|
||||||
|
|
||||||
this[moduleName] = ModuleUnderTest(
|
this[moduleName] = ModuleUnderTest(
|
||||||
|
|||||||
+124
-276
@@ -5,54 +5,20 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.test.ir
|
package org.jetbrains.kotlin.js.test.ir
|
||||||
|
|
||||||
import com.intellij.openapi.vfs.StandardFileSystems
|
|
||||||
import com.intellij.openapi.vfs.VirtualFileManager
|
|
||||||
import com.intellij.openapi.vfs.local.CoreLocalFileSystem
|
|
||||||
import com.intellij.psi.PsiManager
|
|
||||||
import com.intellij.psi.SingleRootFileViewProvider
|
|
||||||
import com.intellij.testFramework.TestDataFile
|
import com.intellij.testFramework.TestDataFile
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
import org.jetbrains.kotlin.cli.js.K2JsIrCompiler
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
|
||||||
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
|
|
||||||
import org.jetbrains.kotlin.cli.js.klib.*
|
import org.jetbrains.kotlin.cli.js.klib.*
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
|
||||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.*
|
import org.jetbrains.kotlin.ir.backend.js.*
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ic.CacheUpdater
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ic.JsExecutableProducer
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.CompilationOutputs
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
|
|
||||||
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageConfig
|
|
||||||
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageLogLevel
|
|
||||||
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageMode
|
|
||||||
import org.jetbrains.kotlin.ir.linkage.partial.setupPartialLinkageConfig
|
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
|
||||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
|
||||||
import org.jetbrains.kotlin.js.testOld.V8IrJsTestChecker
|
import org.jetbrains.kotlin.js.testOld.V8IrJsTestChecker
|
||||||
import org.jetbrains.kotlin.klib.PartialLinkageTestUtils
|
import org.jetbrains.kotlin.klib.PartialLinkageTestUtils
|
||||||
import org.jetbrains.kotlin.klib.PartialLinkageTestUtils.Dependencies
|
import org.jetbrains.kotlin.klib.PartialLinkageTestUtils.Dependencies
|
||||||
import org.jetbrains.kotlin.klib.PartialLinkageTestUtils.MAIN_MODULE_NAME
|
import org.jetbrains.kotlin.klib.PartialLinkageTestUtils.MAIN_MODULE_NAME
|
||||||
import org.jetbrains.kotlin.klib.PartialLinkageTestUtils.ModuleBuildDirs
|
import org.jetbrains.kotlin.klib.PartialLinkageTestUtils.ModuleBuildDirs
|
||||||
import org.jetbrains.kotlin.konan.file.ZipFileSystemCacheableAccessor
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
|
||||||
import org.jetbrains.kotlin.test.utils.TestDisposable
|
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.nio.charset.Charset
|
|
||||||
import kotlin.io.path.createTempDirectory
|
import kotlin.io.path.createTempDirectory
|
||||||
|
|
||||||
abstract class AbstractJsPartialLinkageNoICTestCase : AbstractJsPartialLinkageTestCase(CompilerType.K1_NO_IC)
|
abstract class AbstractJsPartialLinkageNoICTestCase : AbstractJsPartialLinkageTestCase(CompilerType.K1_NO_IC)
|
||||||
@@ -60,56 +26,44 @@ abstract class AbstractJsPartialLinkageNoICES6TestCase : AbstractJsPartialLinkag
|
|||||||
abstract class AbstractJsPartialLinkageWithICTestCase : AbstractJsPartialLinkageTestCase(CompilerType.K1_WITH_IC)
|
abstract class AbstractJsPartialLinkageWithICTestCase : AbstractJsPartialLinkageTestCase(CompilerType.K1_WITH_IC)
|
||||||
abstract class AbstractFirJsPartialLinkageNoICTestCase : AbstractJsPartialLinkageTestCase(CompilerType.K2_NO_IC)
|
abstract class AbstractFirJsPartialLinkageNoICTestCase : AbstractJsPartialLinkageTestCase(CompilerType.K2_NO_IC)
|
||||||
|
|
||||||
abstract class AbstractJsPartialLinkageTestCase(val compilerType: CompilerType) {
|
abstract class AbstractJsPartialLinkageTestCase(private val compilerType: CompilerType) {
|
||||||
enum class CompilerType(val es6Mode: Boolean) {
|
enum class CompilerType(val useFir: Boolean, val es6Mode: Boolean, val useIc: Boolean) {
|
||||||
K1_NO_IC(false),
|
K1_NO_IC(useFir = false, es6Mode = false, useIc = false),
|
||||||
K1_NO_IC_WITH_ES6(true),
|
K1_NO_IC_WITH_ES6(useFir = false, es6Mode = true, useIc = false),
|
||||||
K1_WITH_IC(false),
|
K1_WITH_IC(useFir = false, es6Mode = false, useIc = true),
|
||||||
K2_NO_IC(false)
|
K2_NO_IC(useFir = true, es6Mode = false, useIc = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val zipAccessor = ZipFileSystemCacheableAccessor(2)
|
private val buildDir: File = createTempDirectory().toFile().also { it.mkdirs() }
|
||||||
private val buildDir = createTempDirectory().toFile().also { it.mkdirs() }
|
|
||||||
private val environment =
|
|
||||||
KotlinCoreEnvironment.createForParallelTests(TestDisposable(), CompilerConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES)
|
|
||||||
|
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun clearArtifacts() {
|
fun clearArtifacts() {
|
||||||
zipAccessor.reset()
|
|
||||||
buildDir.deleteRecursively()
|
buildDir.deleteRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createConfig(moduleName: String): CompilerConfiguration {
|
|
||||||
val config = environment.configuration.copy()
|
|
||||||
config.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
|
|
||||||
config.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
|
|
||||||
config.put(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION, true)
|
|
||||||
|
|
||||||
zipAccessor.reset()
|
|
||||||
config.put(JSConfigurationKeys.ZIP_FILE_SYSTEM_ACCESSOR, zipAccessor)
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class JsTestConfiguration(testPath: String) : PartialLinkageTestUtils.TestConfiguration {
|
private inner class JsTestConfiguration(testPath: String) : PartialLinkageTestUtils.TestConfiguration {
|
||||||
override val testDir: File = File(testPath).absoluteFile
|
override val testDir: File = File(testPath).absoluteFile
|
||||||
override val buildDir: File get() = this@AbstractJsPartialLinkageTestCase.buildDir
|
override val buildDir: File get() = this@AbstractJsPartialLinkageTestCase.buildDir
|
||||||
override val stdlibFile: File get() = File("libraries/stdlib/js-ir/build/classes/kotlin/js/main").absoluteFile
|
override val stdlibFile: File get() = File("libraries/stdlib/js-ir/build/classes/kotlin/js/main").absoluteFile
|
||||||
override val testModeConstructorParameters = mapOf("isJs" to "true")
|
override val testModeConstructorParameters = mapOf("isJs" to "true")
|
||||||
|
|
||||||
|
override fun customizeModuleSources(moduleName: String, moduleSourceDir: File) {
|
||||||
|
if (moduleName == MAIN_MODULE_NAME)
|
||||||
|
customizeMainModuleSources(moduleSourceDir)
|
||||||
|
}
|
||||||
|
|
||||||
override fun buildKlib(
|
override fun buildKlib(
|
||||||
moduleName: String,
|
moduleName: String,
|
||||||
buildDirs: ModuleBuildDirs,
|
buildDirs: ModuleBuildDirs,
|
||||||
dependencies: Dependencies,
|
dependencies: Dependencies,
|
||||||
klibFile: File
|
klibFile: File,
|
||||||
) = this@AbstractJsPartialLinkageTestCase.buildKlib(moduleName, buildDirs, dependencies, klibFile)
|
) = this@AbstractJsPartialLinkageTestCase.buildKlib(moduleName, buildDirs, dependencies, klibFile)
|
||||||
|
|
||||||
override fun buildBinaryAndRun(mainModuleKlibFile: File, dependencies: Dependencies) =
|
override fun buildBinaryAndRun(mainModuleKlibFile: File, dependencies: Dependencies) =
|
||||||
this@AbstractJsPartialLinkageTestCase.buildBinaryAndRun(mainModuleKlibFile, dependencies)
|
this@AbstractJsPartialLinkageTestCase.buildBinaryAndRun(mainModuleKlibFile, dependencies)
|
||||||
|
|
||||||
override fun onNonEmptyBuildDirectory(directory: File) {
|
override fun onNonEmptyBuildDirectory(directory: File) {
|
||||||
zipAccessor.reset()
|
|
||||||
directory.listFiles()?.forEach(File::deleteRecursively)
|
directory.listFiles()?.forEach(File::deleteRecursively)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,260 +75,154 @@ abstract class AbstractJsPartialLinkageTestCase(val compilerType: CompilerType)
|
|||||||
// The entry point to generated test classes.
|
// The entry point to generated test classes.
|
||||||
fun runTest(@TestDataFile testPath: String) = PartialLinkageTestUtils.runTest(JsTestConfiguration(testPath))
|
fun runTest(@TestDataFile testPath: String) = PartialLinkageTestUtils.runTest(JsTestConfiguration(testPath))
|
||||||
|
|
||||||
private fun buildKlib(moduleName: String, buildDirs: ModuleBuildDirs, dependencies: Dependencies, klibFile: File) {
|
private fun customizeMainModuleSources(moduleSourceDir: File) {
|
||||||
buildDirs.sourceDir.walkTopDown()
|
// Add @JsExport annotation to make box function be visible for Node.
|
||||||
.filter { file -> file.isFile && file.extension == "js" }
|
moduleSourceDir.walkTopDown().forEach { file ->
|
||||||
.forEach { file -> file.copyTo(buildDirs.outputDir.resolve(file.relativeTo(buildDirs.sourceDir)), overwrite = true) }
|
if (file.extension == "kt") {
|
||||||
|
var modified = false
|
||||||
when (compilerType) {
|
val lines = file.readLines().map { line ->
|
||||||
CompilerType.K1_NO_IC, CompilerType.K1_NO_IC_WITH_ES6, CompilerType.K1_WITH_IC ->
|
if (line.startsWith("fun $BOX_FUN_FQN()")) {
|
||||||
buildKlibWithK1(moduleName, buildDirs.sourceDir, dependencies, klibFile)
|
modified = true
|
||||||
CompilerType.K2_NO_IC -> buildKlibWithK2(moduleName, buildDirs.sourceDir, dependencies, klibFile)
|
"@OptIn(ExperimentalJsExport::class) @JsExport $line"
|
||||||
|
} else
|
||||||
|
line
|
||||||
|
}
|
||||||
|
if (modified) file.writeText(lines.joinToString("\n"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildKlibWithK1(moduleName: String, moduleSourceDir: File, dependencies: Dependencies, klibFile: File) {
|
fun buildKlib(moduleName: String, buildDirs: ModuleBuildDirs, dependencies: Dependencies, klibFile: File) {
|
||||||
val config = createConfig(moduleName)
|
val kotlinSourceFilePaths = mutableListOf<String>()
|
||||||
val ktFiles = environment.createPsiFiles(moduleSourceDir)
|
|
||||||
|
|
||||||
val regularDependencies = dependencies.regularDependencies.map { it.libraryFile.absolutePath }
|
buildDirs.sourceDir.walkTopDown().forEach { sourceFile ->
|
||||||
val friendDependencies = dependencies.friendDependencies.map { it.libraryFile.absolutePath }
|
if (sourceFile.isFile) when (sourceFile.extension) {
|
||||||
|
"kt" -> kotlinSourceFilePaths += sourceFile.absolutePath
|
||||||
val moduleStructure = prepareAnalyzedSourceModule(
|
"js" -> {
|
||||||
environment.project,
|
// This is needed to preserve *.js files from test data which are required for tests with `external` declarations:
|
||||||
ktFiles,
|
sourceFile.copyTo(buildDirs.outputDir.resolve(sourceFile.relativeTo(buildDirs.sourceDir)), overwrite = true)
|
||||||
config,
|
}
|
||||||
regularDependencies,
|
}
|
||||||
friendDependencies,
|
|
||||||
AnalyzerWithCompilerReport(config)
|
|
||||||
)
|
|
||||||
|
|
||||||
val moduleSourceFiles = (moduleStructure.mainModule as MainModule.SourceFiles).files
|
|
||||||
val icData = moduleStructure.compilerConfiguration.incrementalDataProvider?.getSerializedData(moduleSourceFiles) ?: emptyList()
|
|
||||||
val expectDescriptorToSymbol = mutableMapOf<DeclarationDescriptor, IrSymbol>()
|
|
||||||
val (moduleFragment, _) = generateIrForKlibSerialization(
|
|
||||||
environment.project,
|
|
||||||
moduleSourceFiles,
|
|
||||||
config,
|
|
||||||
moduleStructure.jsFrontEndResult.jsAnalysisResult,
|
|
||||||
sortDependencies(moduleStructure.moduleDependencies),
|
|
||||||
icData,
|
|
||||||
expectDescriptorToSymbol,
|
|
||||||
IrFactoryImpl,
|
|
||||||
verifySignatures = true
|
|
||||||
) {
|
|
||||||
moduleStructure.getModuleDescriptor(it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val metadataSerializer =
|
// Build KLIB:
|
||||||
KlibMetadataIncrementalSerializer(config, moduleStructure.project, moduleStructure.jsFrontEndResult.hasErrors)
|
runCompilerViaCLI(
|
||||||
|
listOf(
|
||||||
generateKLib(
|
"-Xir-produce-klib-file",
|
||||||
moduleStructure,
|
"-ir-output-dir", klibFile.parentFile.absolutePath,
|
||||||
klibFile.path,
|
"-ir-output-name", moduleName
|
||||||
nopack = false,
|
),
|
||||||
jsOutputName = moduleName,
|
dependencies.toCompilerArgs(),
|
||||||
icData = icData,
|
listOf("-language-version", "2.0").takeIf { compilerType.useFir },
|
||||||
expectDescriptorToSymbol = expectDescriptorToSymbol,
|
kotlinSourceFilePaths
|
||||||
moduleFragment = moduleFragment
|
|
||||||
) { file ->
|
|
||||||
metadataSerializer.serializeScope(file, moduleStructure.jsFrontEndResult.bindingContext, moduleFragment.descriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildKlibWithK2(moduleName: String, moduleSourceDir: File, dependencies: Dependencies, klibFile: File) {
|
|
||||||
val config = createConfig(moduleName)
|
|
||||||
val ktFiles = environment.createPsiFiles(moduleSourceDir)
|
|
||||||
|
|
||||||
val regularDependencies = dependencies.regularDependencies.map { it.libraryFile.absolutePath }
|
|
||||||
val friendDependencies = dependencies.friendDependencies.map { it.libraryFile.absolutePath }
|
|
||||||
|
|
||||||
val diagnosticsReporter = DiagnosticReporterFactory.createPendingReporter()
|
|
||||||
|
|
||||||
val moduleStructure = ModulesStructure(
|
|
||||||
project = environment.project,
|
|
||||||
mainModule = MainModule.SourceFiles(ktFiles),
|
|
||||||
compilerConfiguration = config,
|
|
||||||
dependencies = regularDependencies,
|
|
||||||
friendDependenciesPaths = friendDependencies
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val outputStream = ByteArrayOutputStream()
|
|
||||||
val messageCollector = PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.PLAIN_FULL_PATHS, true)
|
|
||||||
|
|
||||||
val analyzedOutput = compileModuleToAnalyzedFirWithPsi(
|
|
||||||
moduleStructure = moduleStructure,
|
|
||||||
ktFiles = ktFiles,
|
|
||||||
libraries = regularDependencies,
|
|
||||||
friendLibraries = friendDependencies,
|
|
||||||
diagnosticsReporter = diagnosticsReporter,
|
|
||||||
incrementalDataProvider = null,
|
|
||||||
lookupTracker = null
|
|
||||||
)
|
|
||||||
|
|
||||||
val fir2IrActualizedResult = transformFirToIr(moduleStructure, analyzedOutput.output, diagnosticsReporter)
|
|
||||||
|
|
||||||
if (analyzedOutput.reportCompilationErrors(moduleStructure, diagnosticsReporter, messageCollector)) {
|
|
||||||
val messages = outputStream.toByteArray().toString(Charset.forName("UTF-8"))
|
|
||||||
throw AssertionError("The following errors occurred compiling test:\n$messages")
|
|
||||||
}
|
|
||||||
|
|
||||||
serializeFirKlib(
|
|
||||||
moduleStructure = moduleStructure,
|
|
||||||
firOutputs = analyzedOutput.output,
|
|
||||||
fir2IrActualizedResult = fir2IrActualizedResult,
|
|
||||||
outputKlibPath = klibFile.absolutePath,
|
|
||||||
messageCollector = messageCollector,
|
|
||||||
diagnosticsReporter = diagnosticsReporter,
|
|
||||||
jsOutputName = moduleName
|
|
||||||
)
|
|
||||||
|
|
||||||
if (messageCollector.hasErrors()) {
|
|
||||||
val messages = outputStream.toByteArray().toString(Charset.forName("UTF-8"))
|
|
||||||
throw AssertionError("The following errors occurred serializing test klib:\n$messages")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Dependencies) {
|
private fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Dependencies) {
|
||||||
val configuration = createConfig(MAIN_MODULE_NAME)
|
// The modules in `Dependencies.regularDependencies` are already in topological order.
|
||||||
configuration.setupPartialLinkageConfig(PartialLinkageConfig(PartialLinkageMode.ENABLE, PartialLinkageLogLevel.WARNING))
|
// It is important to pass the provided and the produced JS files to Node in exactly the same order.
|
||||||
|
val knownModulesInTopologicalOrder: List<ModuleDetails> = allDependencies.regularDependencies.map { dependency ->
|
||||||
val compilationOutputs = when (compilerType) {
|
ModuleDetails(name = dependency.moduleName, outputDir = dependency.libraryFile.parentFile)
|
||||||
CompilerType.K1_NO_IC, CompilerType.K1_NO_IC_WITH_ES6, CompilerType.K2_NO_IC ->
|
|
||||||
buildBinaryNoIC(configuration, mainModuleKlibFile, allDependencies, compilerType.es6Mode)
|
|
||||||
CompilerType.K1_WITH_IC -> buildBinaryWithIC(configuration, mainModuleKlibFile, allDependencies)
|
|
||||||
}
|
}
|
||||||
|
val knownModuleNames: Set<ModuleName> = knownModulesInTopologicalOrder.mapTo(hashSetOf(), ModuleDetails::name)
|
||||||
|
|
||||||
val binariesDir = File(buildDir, BIN_DIR_NAME).also { it.mkdirs() }
|
val binariesDir: File = File(buildDir, BIN_DIR_NAME).also { it.mkdirs() }
|
||||||
|
|
||||||
// key = module name, value = list of produced JS files
|
runCompilerViaCLI(
|
||||||
val producedBinaries: Map<String, List<File>> = compilationOutputs
|
listOf(
|
||||||
.writeAll(binariesDir, MAIN_MODULE_NAME, false, MAIN_MODULE_NAME, ModuleKind.PLAIN)
|
"-Xir-produce-js",
|
||||||
.filter { file -> file.extension == "js" }
|
"-Xir-per-module",
|
||||||
.groupByTo(linkedMapOf()) { file -> file.nameWithoutExtension.toInnerName() }
|
"-module-kind", "plain",
|
||||||
|
"-Xinclude=${mainModuleKlibFile.absolutePath}",
|
||||||
|
"-ir-output-dir", binariesDir.absolutePath,
|
||||||
|
"-ir-output-name", MAIN_MODULE_NAME
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
"-Xcache-directory",
|
||||||
|
buildDir.resolve("libs-cache").absolutePath
|
||||||
|
).takeIf { compilerType.useIc },
|
||||||
|
allDependencies.toCompilerArgs(),
|
||||||
|
listOf("-Xes-classes").takeIf { compilerType.es6Mode },
|
||||||
|
listOf(
|
||||||
|
"-Xpartial-linkage=enable",
|
||||||
|
"-Xpartial-linkage-loglevel=WARNING"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// key = module name, value = list of JS files out of test data
|
// All JS files produced during the compiler call.
|
||||||
val providedBinaries: Map<String, List<File>> =
|
val producedBinaries: Map<ModuleName, File> = binariesDir.walkTopDown()
|
||||||
(allDependencies.regularDependencies.asSequence().map { it.libraryFile } + mainModuleKlibFile)
|
.filter { binaryFile -> binaryFile.extension == "js" }
|
||||||
.mapNotNull { klibFile ->
|
.associateBy { binaryFile -> binaryFile.guessModuleName() }
|
||||||
val moduleName = if (klibFile.extension == "klib") klibFile.nameWithoutExtension else return@mapNotNull null
|
|
||||||
val outputDir = klibFile.parentFile
|
|
||||||
|
|
||||||
val providedJsFiles = outputDir.listFiles()?.filter { it.isFile && it.extension == "js" }.orEmpty()
|
val unexpectedModuleNames = producedBinaries.keys - knownModuleNames
|
||||||
if (providedJsFiles.isEmpty()) return@mapNotNull null
|
|
||||||
|
|
||||||
moduleName to providedJsFiles
|
|
||||||
}.toMap()
|
|
||||||
|
|
||||||
val unexpectedModuleNames = providedBinaries.keys - producedBinaries.keys
|
|
||||||
check(unexpectedModuleNames.isEmpty()) { "Unexpected module names: $unexpectedModuleNames" }
|
check(unexpectedModuleNames.isEmpty()) { "Unexpected module names: $unexpectedModuleNames" }
|
||||||
|
|
||||||
val allBinaries: List<File> = buildList {
|
val allBinaries: List<File> = buildList {
|
||||||
producedBinaries.forEach { (moduleName, producedJsFiles) ->
|
knownModulesInTopologicalOrder.forEach { moduleDetails ->
|
||||||
this += providedBinaries[moduleName].orEmpty()
|
// A set of JS files directly out of test data (aka "external" JS files) to be used immediately in the application:
|
||||||
this += producedJsFiles
|
val providedBinaries: List<File> = moduleDetails.outputDir.listFiles()?.filter { it.extension == "js" }.orEmpty()
|
||||||
|
addAll(providedBinaries)
|
||||||
|
|
||||||
|
// A JS file produced by the compiler for the given module:
|
||||||
|
val producedBinary: File? = producedBinaries[moduleDetails.name]
|
||||||
|
producedBinary?.let(::add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executeAndCheckBinaries(MAIN_MODULE_NAME, allBinaries)
|
executeAndCheckBinaries(MAIN_MODULE_NAME, allBinaries)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildBinaryNoIC(
|
private fun File.guessModuleName(): String {
|
||||||
configuration: CompilerConfiguration,
|
return when {
|
||||||
mainModuleKlibFile: File,
|
extension != "js" -> error("Not a JS file: $this")
|
||||||
allDependencies: Dependencies,
|
name.startsWith("kotlin-") && "stdlib" in name -> "stdlib"
|
||||||
es6mode: Boolean
|
else -> nameWithoutExtension.removePrefix("kotlin_")
|
||||||
): CompilationOutputs {
|
}
|
||||||
val klib = MainModule.Klib(mainModuleKlibFile.path)
|
|
||||||
val moduleStructure = ModulesStructure(
|
|
||||||
environment.project,
|
|
||||||
klib,
|
|
||||||
configuration,
|
|
||||||
allDependencies.regularDependencies.map { it.libraryFile.path },
|
|
||||||
allDependencies.friendDependencies.map { it.libraryFile.path }
|
|
||||||
)
|
|
||||||
|
|
||||||
val ir = compile(
|
|
||||||
moduleStructure,
|
|
||||||
PhaseConfig(jsPhases),
|
|
||||||
IrFactoryImplForJsIC(WholeWorldStageController()),
|
|
||||||
exportedDeclarations = setOf(BOX_FUN_FQN),
|
|
||||||
granularity = JsGenerationGranularity.PER_MODULE,
|
|
||||||
es6mode = es6mode
|
|
||||||
)
|
|
||||||
|
|
||||||
val transformer = IrModuleToJsTransformer(
|
|
||||||
backendContext = ir.context,
|
|
||||||
mainArguments = emptyList()
|
|
||||||
)
|
|
||||||
|
|
||||||
val compiledResult = transformer.generateModule(
|
|
||||||
modules = ir.allModules,
|
|
||||||
modes = setOf(TranslationMode.PER_MODULE_DEV),
|
|
||||||
relativeRequirePath = false
|
|
||||||
)
|
|
||||||
|
|
||||||
return compiledResult.outputs[TranslationMode.PER_MODULE_DEV] ?: error("No compiler output")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildBinaryWithIC(
|
private fun Dependencies.toCompilerArgs(): List<String> = buildList {
|
||||||
configuration: CompilerConfiguration,
|
if (regularDependencies.isNotEmpty()) {
|
||||||
mainModuleKlibFile: File,
|
this += "-libraries"
|
||||||
allDependencies: Dependencies
|
this += regularDependencies.joinToString(File.pathSeparator) { it.libraryFile.absolutePath }
|
||||||
): CompilationOutputs {
|
}
|
||||||
// TODO: what about friend dependencies?
|
if (friendDependencies.isNotEmpty()) {
|
||||||
val cacheUpdater = CacheUpdater(
|
this += "-Xfriend-modules=${friendDependencies.joinToString(File.pathSeparator) { it.libraryFile.absolutePath }}"
|
||||||
mainModule = mainModuleKlibFile.absolutePath,
|
}
|
||||||
allModules = allDependencies.regularDependencies.map { it.libraryFile.path },
|
|
||||||
mainModuleFriends = emptyList(),
|
|
||||||
cacheDir = buildDir.resolve("libs-cache").absolutePath,
|
|
||||||
compilerConfiguration = configuration,
|
|
||||||
irFactory = { IrFactoryImplForJsIC(WholeWorldStageController()) },
|
|
||||||
mainArguments = null,
|
|
||||||
compilerInterfaceFactory = { mainModule, cfg ->
|
|
||||||
JsIrCompilerWithIC(mainModule, cfg, JsGenerationGranularity.PER_MODULE, PhaseConfig(jsPhases), setOf(BOX_FUN_FQN))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
val icCaches = cacheUpdater.actualizeCaches()
|
|
||||||
|
|
||||||
val mainModuleName = icCaches.last().moduleExternalName
|
|
||||||
val jsExecutableProducer = JsExecutableProducer(
|
|
||||||
mainModuleName = mainModuleName,
|
|
||||||
moduleKind = configuration[JSConfigurationKeys.MODULE_KIND]!!,
|
|
||||||
sourceMapsInfo = SourceMapsInfo.from(configuration),
|
|
||||||
caches = icCaches,
|
|
||||||
relativeRequirePath = true
|
|
||||||
)
|
|
||||||
|
|
||||||
return jsExecutableProducer.buildExecutable(granularity = JsGenerationGranularity.PER_MODULE, outJsProgram = true).compilationOut
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val IrModuleFragment.exportName get() = "kotlin_${name.asStringStripSpecialMarkers()}"
|
private fun runCompilerViaCLI(vararg compilerArgs: List<String?>?) {
|
||||||
private fun String.toInnerName() = if (startsWith("kotlin_")) substringAfter("kotlin_") else this
|
val allCompilerArgs = compilerArgs.flatMap { args -> args.orEmpty().filterNotNull() }.toTypedArray()
|
||||||
|
|
||||||
private fun KotlinCoreEnvironment.createPsiFiles(sourceDir: File): List<KtFile> {
|
val compilerXmlOutput = ByteArrayOutputStream()
|
||||||
val psiManager = PsiManager.getInstance(project)
|
val exitCode = PrintStream(compilerXmlOutput).use { printStream ->
|
||||||
val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem
|
K2JsIrCompiler().execFullPathsInMessages(printStream, allCompilerArgs)
|
||||||
|
}
|
||||||
|
|
||||||
return sourceDir.walkTopDown().filter { file ->
|
if (exitCode != ExitCode.OK)
|
||||||
file.isFile && file.extension == "kt"
|
throw AssertionError(
|
||||||
}.flatMap { file ->
|
buildString {
|
||||||
val virtualFile = fileSystem.findFileByIoFile(file) ?: error("VirtualFile for $file not found")
|
appendLine("Compiler failure.")
|
||||||
SingleRootFileViewProvider(psiManager, virtualFile).allFiles
|
appendLine("Exit code = $exitCode.")
|
||||||
}.filterIsInstance<KtFile>().toList()
|
appendLine("Compiler messages:")
|
||||||
|
appendLine("==========")
|
||||||
|
appendLine(compilerXmlOutput.toString(Charsets.UTF_8.name()))
|
||||||
|
appendLine("==========")
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun File.binJsFile(name: String): File = File(this, "$name.js")
|
|
||||||
|
|
||||||
private fun executeAndCheckBinaries(mainModuleName: String, dependencies: Collection<File>) {
|
private fun executeAndCheckBinaries(mainModuleName: String, dependencies: Collection<File>) {
|
||||||
val checker = V8IrJsTestChecker
|
val checker = V8IrJsTestChecker
|
||||||
|
|
||||||
val filePaths = dependencies.map { it.canonicalPath }
|
val filePaths = dependencies.map { it.canonicalPath }
|
||||||
checker.check(filePaths, mainModuleName, null, BOX_FUN_FQN.asString(), "OK", withModuleSystem = false)
|
checker.check(filePaths, mainModuleName, null, BOX_FUN_FQN, "OK", withModuleSystem = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BIN_DIR_NAME = "_bins_js"
|
private const val BIN_DIR_NAME = "_bins_js"
|
||||||
private val BOX_FUN_FQN = FqName("box")
|
private const val BOX_FUN_FQN = "box"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private typealias ModuleName = String
|
||||||
|
private class ModuleDetails(val name: ModuleName, val outputDir: File)
|
||||||
|
|||||||
Reference in New Issue
Block a user