Command line interface

This commit is contained in:
Svyatoslav Kuzmich
2019-04-18 14:37:25 +03:00
parent 89500d776d
commit bb3b9a935f
7 changed files with 431 additions and 2 deletions
+2
View File
@@ -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"))
@@ -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)
@@ -84,6 +84,15 @@ public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
private static final Map<String, ModuleKind> moduleKindMap = new HashMap<>();
private static final Map<String, SourceMapSourceEmbedding> 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<K2JSCompilerArguments> {
) {
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<K2JSCompilerArguments> {
@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) {
@@ -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<K2JSCompilerArguments>() {
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<String>) {
CLITool.doMain(K2JsIrCompiler(), args)
}
private fun reportCompiledSourcesList(messageCollector: MessageCollector, sourceFiles: List<KtFile>) {
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<String> =
libraryString?.splitByPathSeparator() ?: emptyList()
private fun String.splitByPathSeparator(): List<String> {
return this.split(File.pathSeparator.toRegex())
.dropLastWhile { it.isEmpty() }
.toTypedArray()
.filterNot { it.isEmpty() }
}
}
}
@@ -18,7 +18,7 @@ fun compile(
project: Project,
files: List<KtFile>,
configuration: CompilerConfiguration,
phaseConfig: PhaseConfig,
phaseConfig: PhaseConfig = PhaseConfig(jsPhases),
immediateDependencies: List<KlibModuleRef>,
allDependencies: List<KlibModuleRef>
): String {
+2
View File
@@ -2,6 +2,8 @@ Usage: kotlinc-js <options> <source files>
where advanced options include:
-Xfriend-modules=<path> 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
@@ -567,8 +567,20 @@ open class Kotlin2JsCompile : AbstractKotlinCompile<K2JSCompilerArguments>(), 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 {