diff --git a/compiler/cli/build.gradle.kts b/compiler/cli/build.gradle.kts index 9fca7fee24a..e6f13c1531e 100644 --- a/compiler/cli/build.gradle.kts +++ b/compiler/cli/build.gradle.kts @@ -14,6 +14,8 @@ dependencies { compile(project(":compiler:backend")) compile(project(":compiler:backend.jvm")) compile(project(":compiler:ir.backend.common")) + compile(project(":compiler:ir.serialization.js")) + compile(project(":compiler:backend.js")) compile(project(":compiler:light-classes")) compile(project(":compiler:serialization")) compile(project(":compiler:plugin-api")) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index fb9d401e360..fcab56fa876 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -103,6 +103,18 @@ class K2JSCompilerArguments : CommonCompilerArguments() { // Advanced options + + @Argument(value = "-Xir", description = "Use IR backend") + var irBackend: Boolean by FreezableVar(false) + + @Argument( + value = "-Xir-produce-only", + valueDescription = "{ klib, js }", + description = "Type of output to produce. Overrides -meta-info argument." + ) + var irProduceOnly: String? by NullableStringFreezableVar(null) + + @GradleOption(DefaultValues.BooleanTrueDefault::class) @Argument(value = "-Xtyped-arrays", description = "Translate primitive arrays to JS typed arrays") var typedArrays: Boolean by FreezableVar(true) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java b/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java index cbb57f2ddde..e466922e259 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java @@ -84,6 +84,15 @@ public class K2JSCompiler extends CLICompiler { private static final Map moduleKindMap = new HashMap<>(); private static final Map sourceMapContentEmbeddingMap = new LinkedHashMap<>(); + private K2JsIrCompiler irCompiler = null; + + @NotNull + private K2JsIrCompiler getIrCompiler() { + if (irCompiler == null) + irCompiler = new K2JsIrCompiler(); + return irCompiler; + } + static { moduleKindMap.put(K2JsArgumentConstants.MODULE_PLAIN, ModuleKind.PLAIN); moduleKindMap.put(K2JsArgumentConstants.MODULE_COMMONJS, ModuleKind.COMMON_JS); @@ -163,6 +172,10 @@ public class K2JSCompiler extends CLICompiler { ) { MessageCollector messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); + if (arguments.getIrBackend()) { + return getIrCompiler().doExecute(arguments, configuration, rootDisposable, paths); + } + if (arguments.getFreeArgs().isEmpty() && !IncrementalCompilation.isEnabledForJs()) { if (arguments.getVersion()) { return OK; @@ -362,6 +375,11 @@ public class K2JSCompiler extends CLICompiler { @NotNull CompilerConfiguration configuration, @NotNull K2JSCompilerArguments arguments, @NotNull Services services ) { + if (arguments.getIrBackend()) { + getIrCompiler().setupPlatformSpecificArgumentsAndServices(configuration, arguments, services); + return; + } + MessageCollector messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY); if (arguments.getTarget() != null) { diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt new file mode 100644 index 00000000000..d7838442118 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -0,0 +1,383 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.cli.js + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +import org.jetbrains.kotlin.cli.common.* +import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR +import org.jetbrains.kotlin.cli.common.ExitCode.OK +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants +import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.messages.MessageUtil +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.IncrementalCompilation +import org.jetbrains.kotlin.config.Services +import org.jetbrains.kotlin.incremental.components.ExpectActualTracker +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider +import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer +import org.jetbrains.kotlin.ir.backend.js.KlibModuleRef +import org.jetbrains.kotlin.ir.backend.js.generateKLib +import org.jetbrains.kotlin.ir.backend.js.compile +import org.jetbrains.kotlin.js.config.EcmaVersion +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding +import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.serialization.js.ModuleKind +import org.jetbrains.kotlin.utils.JsMetadataVersion +import org.jetbrains.kotlin.utils.KotlinPaths +import org.jetbrains.kotlin.utils.join +import java.io.File +import java.io.IOException +import java.util.zip.ZipFile + +enum class ProduceKind { + DEFAULT, // Determine what to produce based on js-v1 options + JS, + KLIB +} + +class K2JsIrCompiler : CLICompiler() { + + override val performanceManager: CommonCompilerPerformanceManager = + object : CommonCompilerPerformanceManager("Kotlin to JS (IR) Compiler") {} + + override fun createArguments(): K2JSCompilerArguments { + return K2JSCompilerArguments() + } + + private fun extractKlibFromZip(library: String, messageCollector: MessageCollector): File? { + val zipLib = ZipFile(library) + val tempDir = createTempDir(File(library).name, "klibjar") + tempDir.deleteOnExit() + var extractedKlibDir: File? = null + for (entry in zipLib.entries()) { + if (!entry.isDirectory) { + zipLib.getInputStream(entry).use { input -> + val outputEntryFile = File(tempDir, entry.name) + outputEntryFile.parentFile.mkdirs() + outputEntryFile.outputStream().use { output -> + input.copyTo(output) + } + } + } else { + if (entry.name.endsWith("KLIB/")) { + extractedKlibDir = File(tempDir, entry.name) + messageCollector.report(INFO, "Klib $library is extracted into $extractedKlibDir") + } + } + } + + return extractedKlibDir + } + + private fun loadIrLibrary(library: String, messageCollector: MessageCollector): KlibModuleRef? { + val libraryFile = File(library) + var klibDir = when { + FileUtil.isJarOrZip(libraryFile) -> { + extractKlibFromZip(library, messageCollector) ?: return null + } + !libraryFile.isDirectory -> { + messageCollector.report(ERROR, "Klib $library must be a directory") + return null + } + else -> + libraryFile + } + + var klibFiles = klibDir.listFiles() + if (klibFiles.isEmpty()) { + messageCollector.report(STRONG_WARNING, "Klib $library directory is empty") + return null + } + + // Sometines gradle gives us a directory with klib directory inside + val klibDirInsideDir = klibFiles.find { it.name.endsWith("KLIB") } + if (klibDirInsideDir != null) { + klibDir = klibDirInsideDir + klibFiles = klibDir.listFiles() + } + + val metadataFile = klibFiles.find { it.extension == "klm" } + + if (metadataFile == null) { + messageCollector.report(STRONG_WARNING, "No metadata file (.klm) for klib: $klibDir") + return null + } + + return KlibModuleRef(metadataFile.nameWithoutExtension, klibDir.absolutePath) + } + + override fun doExecute( + arguments: K2JSCompilerArguments, + configuration: CompilerConfiguration, + rootDisposable: Disposable, + paths: KotlinPaths? + ): ExitCode { + val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + + if (arguments.freeArgs.isEmpty() && !IncrementalCompilation.isEnabledForJs()) { + if (arguments.version) { + return OK + } + messageCollector.report(ERROR, "Specify at least one source file or directory", null) + return COMPILATION_ERROR + } + + val pluginLoadResult = PluginCliParser.loadPluginsSafe( + arguments.pluginClasspaths, + arguments.pluginOptions, + configuration + ) + if (pluginLoadResult != ExitCode.OK) return pluginLoadResult + + val libraries = configureLibraries(arguments.libraries) + + configuration.put(JSConfigurationKeys.LIBRARIES, libraries) + configuration.put(JSConfigurationKeys.TRANSITIVE_LIBRARIES, libraries) + + val commonSourcesArray = arguments.commonSources + val commonSources = commonSourcesArray?.toSet() ?: emptySet() + for (arg in arguments.freeArgs) { + configuration.addKotlinSourceRoot(arg, commonSources.contains(arg)) + } + + val environmentForJS = + KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JS_CONFIG_FILES) + + val project = environmentForJS.project + val sourcesFiles = environmentForJS.getSourceFiles() + + environmentForJS.configuration.put(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE, arguments.allowKotlinPackage) + + if (!checkKotlinPackageUsage(environmentForJS, sourcesFiles)) return ExitCode.COMPILATION_ERROR + + val outputFilePath = arguments.outputFile + if (outputFilePath == null) { + messageCollector.report(ERROR, "IR: Specify output file via -output", null) + return ExitCode.COMPILATION_ERROR + } + + if (messageCollector.hasErrors()) { + return ExitCode.COMPILATION_ERROR + } + + if (sourcesFiles.isEmpty() && !IncrementalCompilation.isEnabledForJs()) { + messageCollector.report(ERROR, "No source files", null) + return COMPILATION_ERROR + } + + if (arguments.verbose) { + reportCompiledSourcesList(messageCollector, sourcesFiles) + } + + val outputFile = File(outputFilePath) + + configuration.put(CommonConfigurationKeys.MODULE_NAME, FileUtil.getNameWithoutExtension(outputFile)) + + val config = JsConfig(project, configuration) + val outputDir: File = outputFile.parentFile ?: outputFile.absoluteFile.parentFile!! + try { + config.configuration.put(JSConfigurationKeys.OUTPUT_DIR, outputDir.canonicalFile) + } catch (e: IOException) { + messageCollector.report(ERROR, "Could not resolve output directory", null) + return ExitCode.COMPILATION_ERROR + } + + // TODO: Handle main call parameters + + val dependencies = libraries.flatMap { listOfNotNull(loadIrLibrary(it, messageCollector)) } + .distinctBy { it.moduleName } + + val produceKind = produceMap[arguments.irProduceOnly] + if (produceKind == null) { + messageCollector.report(ERROR, "Unknown produce kind: ${arguments.irProduceOnly}. Valid values are: js, klib") + } + + if (produceKind == ProduceKind.JS || produceKind == ProduceKind.DEFAULT) { + val compiledModule = compile( + project, + sourcesFiles, + configuration, + immediateDependencies = dependencies, + allDependencies = dependencies + ) + + outputFile.writeText(compiledModule) + } + + if (produceKind == ProduceKind.KLIB || (produceKind == ProduceKind.DEFAULT && arguments.metaInfo)) { + val outputKlibPath = "$outputFilePath.KLIB" + generateKLib( + project = config.project, + files = sourcesFiles, + configuration = config.configuration, + immediateDependencies = dependencies, + allDependencies = dependencies, + outputKlibPath = outputKlibPath + ) + } + + return OK + } + + override fun setupPlatformSpecificArgumentsAndServices( + configuration: CompilerConfiguration, + arguments: K2JSCompilerArguments, + services: Services + ) { + val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + + if (arguments.target != null) { + assert("v5" == arguments.target) { "Unsupported ECMA version: " + arguments.target!! } + } + configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.defaultVersion()) + + // TODO: Support source maps + if (arguments.sourceMap) { + messageCollector.report(WARNING, "source-map argument is not supported yet", null) + } else { + if (arguments.sourceMapPrefix != null) { + messageCollector.report(WARNING, "source-map-prefix argument has no effect without source map", null) + } + if (arguments.sourceMapBaseDirs != null) { + messageCollector.report(WARNING, "source-map-source-root argument has no effect without source map", null) + } + } + + if (arguments.metaInfo) { + configuration.put(JSConfigurationKeys.META_INFO, true) + } + + configuration.put(JSConfigurationKeys.TYPED_ARRAYS_ENABLED, arguments.typedArrays) + + configuration.put(JSConfigurationKeys.FRIEND_PATHS_DISABLED, arguments.friendModulesDisabled) + + val friendModules = arguments.friendModules + if (!arguments.friendModulesDisabled && friendModules != null) { + val friendPaths = friendModules + .split(File.pathSeparator.toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + .filterNot { it.isEmpty() } + + configuration.put(JSConfigurationKeys.FRIEND_PATHS, friendPaths) + } + + val moduleKindName = arguments.moduleKind + var moduleKind: ModuleKind? = if (moduleKindName != null) moduleKindMap[moduleKindName] else ModuleKind.PLAIN + if (moduleKind == null) { + messageCollector.report( + ERROR, "Unknown module kind: $moduleKindName. Valid values are: plain, amd, commonjs, umd", null + ) + moduleKind = ModuleKind.PLAIN + } + configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind) + + val incrementalDataProvider = services[IncrementalDataProvider::class.java] + if (incrementalDataProvider != null) { + configuration.put(JSConfigurationKeys.INCREMENTAL_DATA_PROVIDER, incrementalDataProvider) + } + + val incrementalResultsConsumer = services[IncrementalResultsConsumer::class.java] + if (incrementalResultsConsumer != null) { + configuration.put(JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER, incrementalResultsConsumer) + } + + val lookupTracker = services[LookupTracker::class.java] + if (lookupTracker != null) { + configuration.put(CommonConfigurationKeys.LOOKUP_TRACKER, lookupTracker) + } + + val expectActualTracker = services[ExpectActualTracker::class.java] + if (expectActualTracker != null) { + configuration.put(CommonConfigurationKeys.EXPECT_ACTUAL_TRACKER, expectActualTracker) + } + + val sourceMapEmbedContentString = arguments.sourceMapEmbedSources + var sourceMapContentEmbedding: SourceMapSourceEmbedding? = if (sourceMapEmbedContentString != null) + sourceMapContentEmbeddingMap[sourceMapEmbedContentString] + else + SourceMapSourceEmbedding.INLINING + if (sourceMapContentEmbedding == null) { + val message = "Unknown source map source embedding mode: " + sourceMapEmbedContentString + ". Valid values are: " + + StringUtil.join(sourceMapContentEmbeddingMap.keys, ", ") + messageCollector.report(ERROR, message, null) + sourceMapContentEmbedding = SourceMapSourceEmbedding.INLINING + } + configuration.put(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, sourceMapContentEmbedding) + + if (!arguments.sourceMap && sourceMapEmbedContentString != null) { + messageCollector.report(WARNING, "source-map-embed-sources argument has no effect without source map", null) + } + } + + override fun executableScriptFileName(): String { + return "kotlinc-js -Xir" + } + + override fun createMetadataVersion(versionArray: IntArray): BinaryVersion { + // TODO: Support metadata versions for klibs + return JsMetadataVersion(*versionArray) + } + + companion object { + private val moduleKindMap = mapOf( + K2JsArgumentConstants.MODULE_PLAIN to ModuleKind.PLAIN, + K2JsArgumentConstants.MODULE_COMMONJS to ModuleKind.COMMON_JS, + K2JsArgumentConstants.MODULE_AMD to ModuleKind.AMD, + K2JsArgumentConstants.MODULE_UMD to ModuleKind.UMD + ) + private val sourceMapContentEmbeddingMap = mapOf( + K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_ALWAYS to SourceMapSourceEmbedding.ALWAYS, + K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_NEVER to SourceMapSourceEmbedding.NEVER, + K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_INLINING to SourceMapSourceEmbedding.INLINING + ) + private val produceMap = mapOf( + null to ProduceKind.DEFAULT, + "js" to ProduceKind.JS, + "klib" to ProduceKind.KLIB + ) + + @JvmStatic + fun main(args: Array) { + CLITool.doMain(K2JsIrCompiler(), args) + } + + private fun reportCompiledSourcesList(messageCollector: MessageCollector, sourceFiles: List) { + val fileNames = sourceFiles.map { file -> + val virtualFile = file.virtualFile + if (virtualFile != null) { + MessageUtil.virtualFileToPath(virtualFile) + } else { + file.name + " (no virtual file)" + } + } + messageCollector.report(LOGGING, "Compiling source files: " + join(fileNames, ", "), null) + } + + private fun configureLibraries(libraryString: String?): List = + libraryString?.splitByPathSeparator() ?: emptyList() + + private fun String.splitByPathSeparator(): List { + return this.split(File.pathSeparator.toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + .filterNot { it.isEmpty() } + } + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index 201b987ccc3..60252f25d9b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -18,7 +18,7 @@ fun compile( project: Project, files: List, configuration: CompilerConfiguration, - phaseConfig: PhaseConfig, + phaseConfig: PhaseConfig = PhaseConfig(jsPhases), immediateDependencies: List, allDependencies: List ): String { diff --git a/compiler/testData/cli/js/jsExtraHelp.out b/compiler/testData/cli/js/jsExtraHelp.out index da62f1b0172..14cbf7660b6 100644 --- a/compiler/testData/cli/js/jsExtraHelp.out +++ b/compiler/testData/cli/js/jsExtraHelp.out @@ -2,6 +2,8 @@ Usage: kotlinc-js where advanced options include: -Xfriend-modules= Paths to friend modules -Xfriend-modules-disabled Disable internal declaration export + -Xir Use IR backend + -Xir-produce-only={ klib, js } Type of output to produce. Overrides -meta-info argument. -Xtyped-arrays Translate primitive arrays to JS typed arrays -Xallow-kotlin-package Allow compiling code in package 'kotlin' and allow not requiring kotlin.stdlib in module-info -Xallow-result-return-type Allow compiling code when `kotlin.Result` is used as a return type diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 3cd43304dfd..931362df59d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -567,8 +567,20 @@ open class Kotlin2JsCompile : AbstractKotlinCompile(), Ko logger.debug("Calling compiler") destinationDir.mkdirs() + val libraryFilter: (File) -> Boolean + + if ("-Xir" in args.freeArgs) { + logger.kotlinDebug("Using JS IR backend") + incremental = false + + // TODO: Detect IR libraries + libraryFilter = { true } + } else { + libraryFilter = LibraryUtils::isKotlinJavascriptLibrary + } + val dependencies = compileClasspath - .filter { LibraryUtils.isKotlinJavascriptLibrary(it) } + .filter(libraryFilter) .map { it.canonicalPath } args.libraries = (dependencies + listOfNotNull(friendDependency)).distinct().let {