Rewrite interop tools command line options to have help messages without crashes (#2672)
This commit is contained in:
@@ -38,6 +38,9 @@ dependencies {
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
sourceSets {
|
||||
main.kotlin.srcDirs += "$rootDir/tools/kliopt"
|
||||
}
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = ['-Xuse-experimental=kotlin.ExperimentalUnsignedTypes']
|
||||
}
|
||||
|
||||
+5
-3
@@ -6,7 +6,8 @@ import org.jetbrains.kotlin.native.interop.gen.jvm.buildNativeLibrary
|
||||
import org.jetbrains.kotlin.native.interop.gen.jvm.prepareTool
|
||||
import org.jetbrains.kotlin.native.interop.indexer.NativeLibraryHeaders
|
||||
import org.jetbrains.kotlin.native.interop.indexer.getHeaderPaths
|
||||
import org.jetbrains.kotlin.native.interop.tool.CInteropArguments
|
||||
import org.jetbrains.kotlin.native.interop.tool.getCInteropArguments
|
||||
import org.jetbrains.kliopt.ArgParser
|
||||
import java.io.File
|
||||
|
||||
fun defFileDependencies(args: Array<String>) {
|
||||
@@ -38,12 +39,13 @@ private fun makeDependencyAssigner(targets: List<String>, defFiles: List<File>)
|
||||
|
||||
private fun makeDependencyAssignerForTarget(target: String, defFiles: List<File>): SingleTargetDependencyAssigner {
|
||||
val tool = prepareTool(target, KotlinPlatform.NATIVE)
|
||||
|
||||
val argParser = ArgParser(getCInteropArguments(), useDefaultHelpShortName = false)
|
||||
argParser.parse(arrayOf<String>())
|
||||
val libraries = defFiles.associateWith {
|
||||
buildNativeLibrary(
|
||||
tool,
|
||||
DefFile(it, tool.substitutions),
|
||||
CInteropArguments(),
|
||||
argParser,
|
||||
ImportsImpl(emptyMap())
|
||||
).getHeaderPaths()
|
||||
}
|
||||
|
||||
+48
-84
@@ -16,97 +16,61 @@
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.tool
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.arguments.*
|
||||
import org.jetbrains.kliopt.*
|
||||
|
||||
const val HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX = "headerFilterAdditionalSearchPrefix"
|
||||
const val NODEFAULTLIBS = "nodefaultlibs"
|
||||
const val PURGE_USER_LIBS = "Xpurge-user-libs"
|
||||
const val TEMP_DIR = "Xtemporary-files-dir"
|
||||
|
||||
// TODO: unify camel and snake cases.
|
||||
// Possible solution is to accept both cases
|
||||
open class CommonInteropArguments : CommonToolArguments() {
|
||||
@Argument(value = "-flavor", valueDescription = "<flavor>", description = "One of: jvm, native or wasm")
|
||||
var flavor: String? = null
|
||||
fun getCommonInteropArguments() = listOf(
|
||||
OptionDescriptor(ArgType.Boolean(), "verbose", description = "Enable verbose logging output", defaultValue = "false"),
|
||||
OptionDescriptor(ArgType.Choice(listOf("jvm", "native", "wasm")),
|
||||
"flavor", description = "Interop target", defaultValue = "jvm"),
|
||||
OptionDescriptor(ArgType.String(), "pkg", description = "place generated bindings to the package"),
|
||||
OptionDescriptor(ArgType.String(), "output", "o", "specifies the resulting library file", defaultValue = "nativelib"),
|
||||
OptionDescriptor(ArgType.String(), "libraryPath", description = "add a library search path",
|
||||
isMultiple = true, delimiter = ","),
|
||||
OptionDescriptor(ArgType.String(), "staticLibrary", description = "embed static library to the result",
|
||||
isMultiple = true, delimiter = ","),
|
||||
OptionDescriptor(ArgType.String(), "generated", description = "place generated bindings to the directory",
|
||||
defaultValue = System.getProperty("user.dir")),
|
||||
OptionDescriptor(ArgType.String(), "natives", description = "where to put the built native files",
|
||||
defaultValue = System.getProperty("user.dir")),
|
||||
OptionDescriptor(ArgType.String(), "library",
|
||||
description = "library to use for building", isMultiple = true),
|
||||
OptionDescriptor(ArgType.String(), "repo", "r",
|
||||
"repository to resolve dependencies", isMultiple = true),
|
||||
OptionDescriptor(ArgType.Boolean(), NODEFAULTLIBS, description = "don't link the libraries from dist/klib automatically",
|
||||
defaultValue = "false"),
|
||||
OptionDescriptor(ArgType.Boolean(), PURGE_USER_LIBS, description = "don't link unused libraries even explicitly specified",
|
||||
defaultValue = "false"),
|
||||
OptionDescriptor(ArgType.String(), TEMP_DIR, description = "save temporary files to the given directory")
|
||||
)
|
||||
|
||||
@Argument(value = "-pkg", valueDescription = "<fully qualified name>", description = "place generated bindings to the package")
|
||||
var pkg: String? = null
|
||||
|
||||
@Argument(value = "-generated", valueDescription = "<dir>", description = "place generated bindings to the directory")
|
||||
var generated: String? = null
|
||||
|
||||
@Argument(value = "-libraryPath", valueDescription = "<dir>", description = "add a library search path")
|
||||
var libraryPath: Array<String> = arrayOf()
|
||||
|
||||
@Argument(value = "-manifest", valueDescription = "<file>", description = "library manifest addend")
|
||||
var manifest: String? = null
|
||||
|
||||
@Argument(value = "-natives", valueDescription = "<directory>", description = "where to put the built native files")
|
||||
var natives: String? = null
|
||||
|
||||
@Argument(value = "-staticLibrary", valueDescription = "<file>", description = "embed static library to the result")
|
||||
var staticLibrary: Array<String> = arrayOf()
|
||||
|
||||
@Argument(value = "-temporaryFilesDir", valueDescription = "<dir>", description = "Save temporary files to the given directory")
|
||||
var temporaryFilesDir: String? = null
|
||||
fun getCInteropArguments(): List<OptionDescriptor> {
|
||||
val options = listOf(
|
||||
OptionDescriptor(ArgType.String(), "target", description = "native target to compile to", defaultValue = "host"),
|
||||
OptionDescriptor(ArgType.String(), "def", description = "the library definition file"),
|
||||
OptionDescriptor(ArgType.String(), "header", "h", "header file to produce kotlin bindings for",
|
||||
isMultiple = true, delimiter = ",", deprecatedWarning = "Short form -h of option -header is deprecated"),
|
||||
OptionDescriptor(ArgType.String(), HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX, "hfasp",
|
||||
"header file to produce kotlin bindings for", isMultiple = true, delimiter = ","),
|
||||
OptionDescriptor(ArgType.String(), "compilerOpts", "copt",
|
||||
"additional compiler options", isMultiple = true, delimiter = " "),
|
||||
OptionDescriptor(ArgType.String(), "linkerOpts", "lopt",
|
||||
"additional linker options", isMultiple = true, delimiter = " "),
|
||||
OptionDescriptor(ArgType.Boolean(), "shims", description = "wrap bindings by a tracing layer", defaultValue = "false"),
|
||||
OptionDescriptor(ArgType.String(), "linker", description = "use specified linker")
|
||||
)
|
||||
return (options + getCommonInteropArguments())
|
||||
}
|
||||
|
||||
class CInteropArguments : CommonInteropArguments() {
|
||||
@Argument(value = "-import", valueDescription = "<imports>", description = "a semicolon separated list of headers, prepended with the package name")
|
||||
var import: Array<String> = arrayOf()
|
||||
|
||||
@Argument(value = "-target", valueDescription = "<target>", description = "native target to compile to")
|
||||
var target: String? = null
|
||||
|
||||
@Argument(value = "-def", valueDescription = "<file>", description = "the library definition file")
|
||||
var def: String? = null
|
||||
|
||||
// TODO: the short -h for -header conflicts with -h for -help.
|
||||
// The -header currently wins, but need to make it a little more sound.
|
||||
@Argument(value = "-header", shortName = "-h", valueDescription = "<file>", description = "header file to produce kotlin bindings for")
|
||||
var header: Array<String> = arrayOf()
|
||||
|
||||
@Argument(value = HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX, shortName = "-hfasp", valueDescription = "<file>", description = "header file to produce kotlin bindings for")
|
||||
var headerFilterPrefix: Array<String> = arrayOf()
|
||||
|
||||
@Argument(value = "-compilerOpts", shortName = "-copt", valueDescription = "<arg>", description = "additional compiler options", delimiter = " ")
|
||||
var compilerOpts: Array<String> = arrayOf()
|
||||
|
||||
@Argument(value = "-linkerOpts", shortName = "-lopt", valueDescription = "<arg>", description = "additional linker options", delimiter = " ")
|
||||
var linkerOpts: Array<String> = arrayOf()
|
||||
|
||||
@Argument(value = "-shims", description = "wrap bindings by a tracing layer")
|
||||
var shims: Boolean = false
|
||||
|
||||
@Argument(value = "-linker", valueDescription = "<file>", description = "use specified linker")
|
||||
|
||||
var linker: String? = null
|
||||
@Argument(value = "-cstubsname", valueDescription = "<name>", description = "provide a name for the generated c stubs file")
|
||||
var cstubsname: String? = null
|
||||
}
|
||||
|
||||
const val HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX = "-headerFilterAdditionalSearchPrefix"
|
||||
|
||||
fun <T: CommonToolArguments> parseCommandLine(args: Array<String>, arguments: T): T {
|
||||
parseCommandLineArguments(args.asList(), arguments)
|
||||
arguments.errors?.let { reportArgumentParseProblems(it) }
|
||||
return arguments
|
||||
}
|
||||
|
||||
// Integrate with CLITool from the big Kotlin and get rid of the mess below.
|
||||
|
||||
internal fun warn(msg: String) {
|
||||
println("warning: $msg")
|
||||
}
|
||||
|
||||
// This is a copy of CLITool.kt's function adapted to work without a collector.
|
||||
private fun reportArgumentParseProblems(errors: ArgumentParseErrors) {
|
||||
for (flag in errors.unknownExtraFlags) {
|
||||
warn("Flag is not supported by this version of the compiler: $flag")
|
||||
}
|
||||
for (argument in errors.extraArgumentsPassedInObsoleteForm) {
|
||||
warn("Advanced option value is passed in an obsolete form. Please use the '=' character " +
|
||||
"to specify the value: $argument=...")
|
||||
}
|
||||
for ((key, value) in errors.duplicateArguments) {
|
||||
warn("Argument $key is passed multiple times. Only the last value will be used: $value")
|
||||
}
|
||||
for ((deprecatedName, newName) in errors.deprecatedArguments) {
|
||||
warn("Argument $deprecatedName is deprecated. Please use $newName instead")
|
||||
}
|
||||
}
|
||||
fun ArgParser.getValuesAsArray(propertyName: String) =
|
||||
(getAll<String>(propertyName) ?: listOf<String>()).toTypedArray()
|
||||
|
||||
+35
-51
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.native.interop.gen.*
|
||||
import org.jetbrains.kotlin.native.interop.gen.wasm.processIdlLib
|
||||
import org.jetbrains.kotlin.native.interop.indexer.*
|
||||
import org.jetbrains.kotlin.native.interop.tool.*
|
||||
import org.jetbrains.kliopt.ArgParser
|
||||
import java.io.File
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.nio.file.*
|
||||
@@ -32,11 +33,12 @@ fun main(args: Array<String>) {
|
||||
processCLib(args)
|
||||
}
|
||||
|
||||
fun interop(flavor: String, args: Array<String>) = when(flavor) {
|
||||
"jvm", "native" -> processCLib(args)
|
||||
"wasm" -> processIdlLib(args)
|
||||
else -> error("Unexpected flavor")
|
||||
}
|
||||
fun interop(flavor: String, args: Array<String>, additionalArgs: Map<String, Any> = mapOf()) =
|
||||
when(flavor) {
|
||||
"jvm", "native" -> processCLib(args, additionalArgs)
|
||||
"wasm" -> processIdlLib(args, additionalArgs)
|
||||
else -> error("Unexpected flavor")
|
||||
}
|
||||
|
||||
// Options, whose values are space-separated and can be escaped.
|
||||
val escapedOptions = setOf("-compilerOpts", "-linkerOpts")
|
||||
@@ -80,23 +82,6 @@ private fun Properties.putAndRunOnReplace(key: Any, newValue: Any, beforeReplace
|
||||
this[key] = newValue
|
||||
}
|
||||
|
||||
// TODO: Utilize Usage from the big Kotlin.
|
||||
// That requires to extend the CLITool class.
|
||||
private fun usage() {
|
||||
println("""
|
||||
Run interop tool with -def <def_file_for_lib>.def
|
||||
Following flags are supported:
|
||||
-def <file>.def specifies library definition file
|
||||
-compilerOpts <c compiler flags> specifies flags passed to clang
|
||||
-linkerOpts <linker flags> specifies flags passed to linker
|
||||
-verbose <boolean> increases verbosity
|
||||
-shims <boolean> adds generation of shims tracing native library calls
|
||||
-pkg <fully qualified package name> place the resulting definitions into the package
|
||||
-h <file>.h header files to parse
|
||||
-o <file>.klib specifies the resulting library file
|
||||
""")
|
||||
}
|
||||
|
||||
private fun selectNativeLanguage(config: DefFile.DefFileConfig): Language {
|
||||
val languages = mapOf(
|
||||
"C" to Language.C,
|
||||
@@ -173,29 +158,28 @@ private fun findFilesByGlobs(roots: List<Path>, globs: List<String>): Map<Path,
|
||||
}
|
||||
|
||||
|
||||
private fun processCLib(args: Array<String>): Array<String>? {
|
||||
|
||||
val arguments = parseCommandLine(args, CInteropArguments())
|
||||
val userDir = System.getProperty("user.dir")
|
||||
val ktGenRoot = arguments.generated ?: userDir
|
||||
val nativeLibsDir = arguments.natives ?: userDir
|
||||
val flavorName = arguments.flavor ?: "jvm"
|
||||
val flavor = KotlinPlatform.values().single { it.name.equals(flavorName, ignoreCase = true) }
|
||||
val defFile = arguments.def?.let { File(it) }
|
||||
val manifestAddend = arguments.manifest?.let { File(it) }
|
||||
|
||||
if (defFile == null && arguments.pkg == null) {
|
||||
usage()
|
||||
private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> = mapOf()): Array<String>? {
|
||||
val argParser = ArgParser(getCInteropArguments(), useDefaultHelpShortName = false)
|
||||
if (!argParser.parse(args))
|
||||
return null
|
||||
val ktGenRoot = argParser.get<String>("generated")
|
||||
val nativeLibsDir = argParser.get<String>("natives")
|
||||
val flavorName = argParser.get<String>("flavor")
|
||||
val flavor = KotlinPlatform.values().single { it.name.equals(flavorName, ignoreCase = true) }
|
||||
val defFile = argParser.get<String>("def")?.let { File(it) }
|
||||
val manifestAddend = (additionalArgs["manifest"] as? String)?.let { File(it) }
|
||||
|
||||
if (defFile == null && argParser.get<String>("pkg") == null) {
|
||||
argParser.printError("-def or -pkg should provided!")
|
||||
}
|
||||
|
||||
val tool = prepareTool(arguments.target, flavor)
|
||||
val tool = prepareTool(argParser.get<String>("target"), flavor)
|
||||
|
||||
val def = DefFile(defFile, tool.substitutions)
|
||||
|
||||
val additionalLinkerOpts = arguments.linkerOpts
|
||||
val generateShims = arguments.shims
|
||||
val verbose = arguments.verbose
|
||||
val additionalLinkerOpts = argParser.getValuesAsArray("linkerOpts")
|
||||
val generateShims = argParser.get<Boolean>("shims")!!
|
||||
val verbose = argParser.get<Boolean>("verbose")!!
|
||||
|
||||
val language = selectNativeLanguage(def.config)
|
||||
|
||||
@@ -204,14 +188,14 @@ private fun processCLib(args: Array<String>): Array<String>? {
|
||||
def.config.linkerOpts.toTypedArray() +
|
||||
tool.defaultCompilerOpts +
|
||||
additionalLinkerOpts
|
||||
val linkerName = arguments.linker ?: def.config.linker
|
||||
val linkerName = argParser.get<String>("linker") ?: def.config.linker
|
||||
val linker = "${tool.llvmHome}/bin/$linkerName"
|
||||
val compiler = "${tool.llvmHome}/bin/clang"
|
||||
val excludedFunctions = def.config.excludedFunctions.toSet()
|
||||
val excludedMacros = def.config.excludedMacros.toSet()
|
||||
val staticLibraries = def.config.staticLibraries + arguments.staticLibrary
|
||||
val libraryPaths = def.config.libraryPaths + arguments.libraryPath
|
||||
val fqParts = (arguments.pkg ?: def.config.packageName)?.let {
|
||||
val staticLibraries = def.config.staticLibraries + argParser.getValuesAsArray("staticLibrary")
|
||||
val libraryPaths = def.config.libraryPaths + argParser.getValuesAsArray("libraryPath")
|
||||
val fqParts = (argParser.get<String>("pkg") ?: def.config.packageName)?.let {
|
||||
it.split('.')
|
||||
} ?: defFile!!.name.split('.').reversed().drop(1)
|
||||
|
||||
@@ -221,13 +205,13 @@ private fun processCLib(args: Array<String>): Array<String>? {
|
||||
val outKtFileRelative = (fqParts + outKtFileName).joinToString("/")
|
||||
val outKtFile = File(ktGenRoot, outKtFileRelative)
|
||||
|
||||
val libName = arguments.cstubsname ?: fqParts.joinToString("") + "stubs"
|
||||
val libName = (additionalArgs["cstubsname"] as? String)?: fqParts.joinToString("") + "stubs"
|
||||
|
||||
val tempFiles = TempFiles(libName, arguments.temporaryFilesDir)
|
||||
val tempFiles = TempFiles(libName, argParser.get<String>("Xtemporary-files-dir"))
|
||||
|
||||
val imports = parseImports(arguments.import)
|
||||
val imports = parseImports((additionalArgs["import"] as? List<String>)?.toTypedArray() ?: arrayOf())
|
||||
|
||||
val library = buildNativeLibrary(tool, def, arguments, imports)
|
||||
val library = buildNativeLibrary(tool, def, argParser, imports)
|
||||
|
||||
val configuration = InteropConfiguration(
|
||||
library = library,
|
||||
@@ -311,11 +295,11 @@ internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
|
||||
internal fun buildNativeLibrary(
|
||||
tool: ToolConfig,
|
||||
def: DefFile,
|
||||
arguments: CInteropArguments,
|
||||
arguments: ArgParser,
|
||||
imports: ImportsImpl
|
||||
): NativeLibrary {
|
||||
val additionalHeaders = arguments.header
|
||||
val additionalCompilerOpts = arguments.compilerOpts
|
||||
val additionalHeaders = arguments.getValuesAsArray("header")
|
||||
val additionalCompilerOpts = arguments.getValuesAsArray("compilerOpts")
|
||||
|
||||
val headerFiles = def.config.headers + additionalHeaders
|
||||
val language = selectNativeLanguage(def.config)
|
||||
@@ -323,7 +307,7 @@ internal fun buildNativeLibrary(
|
||||
addAll(def.config.compilerOpts)
|
||||
addAll(tool.defaultCompilerOpts)
|
||||
addAll(additionalCompilerOpts)
|
||||
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix, def))
|
||||
addAll(getCompilerFlagsForVfsOverlay(arguments.getValuesAsArray("headerFilterPrefix"), def))
|
||||
addAll(when (language) {
|
||||
Language.C -> emptyList()
|
||||
Language.OBJECTIVE_C -> {
|
||||
|
||||
+13
-11
@@ -3,8 +3,9 @@ package org.jetbrains.kotlin.native.interop.gen.wasm
|
||||
import org.jetbrains.kotlin.konan.file.File
|
||||
import org.jetbrains.kotlin.native.interop.gen.argsToCompiler
|
||||
import org.jetbrains.kotlin.native.interop.gen.wasm.idl.*
|
||||
import org.jetbrains.kotlin.native.interop.tool.CommonInteropArguments
|
||||
import org.jetbrains.kotlin.native.interop.tool.parseCommandLine
|
||||
import org.jetbrains.kliopt.ArgParser
|
||||
import org.jetbrains.kotlin.native.interop.tool.getCommonInteropArguments
|
||||
import org.jetbrains.kotlin.native.interop.tool.getValuesAsArray
|
||||
|
||||
fun kotlinHeader(packageName: String): String {
|
||||
return "package $packageName\n" +
|
||||
@@ -391,21 +392,22 @@ fun generateJs(interfaces: List<Interface>): String =
|
||||
const val idlMathPackage = "kotlinx.interop.wasm.math"
|
||||
const val idlDomPackage = "kotlinx.interop.wasm.dom"
|
||||
|
||||
fun processIdlLib(args: Array<String>): Array<String> {
|
||||
val arguments = parseCommandLine(args, CommonInteropArguments())
|
||||
fun processIdlLib(args: Array<String>, additionalArgs: Map<String, Any> = mapOf()): Array<String>? {
|
||||
val argParser = ArgParser(getCommonInteropArguments(), useDefaultHelpShortName = false)
|
||||
if (!argParser.parse(args))
|
||||
return null
|
||||
// TODO: Refactor me.
|
||||
val userDir = System.getProperty("user.dir")
|
||||
val ktGenRoot = File(arguments.generated ?: userDir).mkdirs()
|
||||
val nativeLibsDir = File(arguments.natives ?: userDir).mkdirs()
|
||||
val ktGenRoot = File(argParser.get<String>("generated")!!).mkdirs()
|
||||
val nativeLibsDir = File(argParser.get<String>("natives")!!).mkdirs()
|
||||
|
||||
val idl = when (arguments.pkg) {
|
||||
val idl = when (argParser.get<String>("pkg")) {
|
||||
idlMathPackage-> idlMath
|
||||
idlDomPackage -> idlDom
|
||||
else -> throw IllegalArgumentException("Please choose either $idlMathPackage or $idlDomPackage for -pkg argument")
|
||||
}
|
||||
|
||||
File(ktGenRoot, "kotlin_stubs.kt").writeText(generateKotlin(arguments.pkg!!, idl))
|
||||
File(ktGenRoot, "kotlin_stubs.kt").writeText(generateKotlin(argParser.get<String>("pkg")!!, idl))
|
||||
File(nativeLibsDir, "js_stubs.js").writeText(generateJs(idl))
|
||||
File(arguments.manifest!!).writeText("") // The manifest is currently unused for wasm.
|
||||
return argsToCompiler(arguments.staticLibrary, arguments.libraryPath)
|
||||
File((additionalArgs["manifest"] as? String)!!).writeText("") // The manifest is currently unused for wasm.
|
||||
return argsToCompiler(argParser.getValuesAsArray("staticLibrary"), argParser.getValuesAsArray("libraryPath"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user