Skip to content

Commit

Permalink
Rewrite interop tools command line options to have help messages with…
Browse files Browse the repository at this point in the history
…out crashes (JetBrains#2672)
  • Loading branch information
LepilkinaElena authored Feb 20, 2019
1 parent 554348a commit 75805b3
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 238 deletions.
3 changes: 3 additions & 0 deletions Interop/StubGenerator/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ dependencies {
}

compileKotlin {
sourceSets {
main.kotlin.srcDirs += "$rootDir/tools/kliopt"
}
kotlinOptions {
freeCompilerArgs = ['-Xuse-experimental=kotlin.ExperimentalUnsignedTypes']
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>) {
Expand Down Expand Up @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

@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
}

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
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")
)

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())
}

// 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()
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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")
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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"
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 = arguments.def?.let { File(it) }
val manifestAddend = arguments.manifest?.let { File(it) }
val defFile = argParser.get<String>("def")?.let { File(it) }
val manifestAddend = (additionalArgs["manifest"] as? String)?.let { File(it) }

if (defFile == null && arguments.pkg == null) {
usage()
return null
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)

Expand All @@ -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)

Expand All @@ -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,
Expand Down Expand Up @@ -311,19 +295,19 @@ 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)
val compilerOpts: List<String> = mutableListOf<String>().apply {
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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" +
Expand Down Expand Up @@ -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"))
}
Loading

0 comments on commit 75805b3

Please sign in to comment.