Improve interop user experience and docs (#342)

This commit is contained in:
Nikolay Igotti
2017-03-15 17:25:10 +03:00
committed by GitHub
parent e67d9d4065
commit f3794aa206
7 changed files with 105 additions and 17 deletions
+56 -2
View File
@@ -1,4 +1,23 @@
# An example of interop tool use # # Kotlin N interoperability #
## Introduction ##
_Kotlin N_ follows general tradition of Kotlin to provide excellent
existing platform software interoperability. In case of native platform
most important interoperability target is a C library. Thus _Kotlin N_
comes with an `interop` tool, which could be used to quickly generate
everything needed to interact with an external library.
Following workflow is expected when interacting with the native library.
* create `.def` file describing what to include into bindings
* use `interop` tool to produce `stubs.bc` and Kotlin bindings
* run _Kotlin N_ compiler on an application to produce the final executable
Interoperability tool analyses C headers and produces "natural" mapping of
types, function and constants into the Kotlin world. Generated stubs can be
imported into an IDE for purposes of code completion and navigation.
## Simple example ##
Build the dependencies and the compiler (see README.md). Build the dependencies and the compiler (see README.md).
@@ -8,7 +27,7 @@ Prepare stubs for the system sockets library:
Compile the echo server: Compile the echo server:
./dist/bin/konanc backend.native/tests/interop/basics/echo_server.kt \ ./dist/bin/kotlinc backend.native/tests/interop/basics/echo_server.kt \
sockets -nativelibrary socketsstubs.bc sockets -nativelibrary socketsstubs.bc
Run the server: Run the server:
@@ -23,3 +42,38 @@ Write something to console and watch server echoing it back.
~~Quit telnet by pressing ctrl+] ctrl+D~~ ~~Quit telnet by pressing ctrl+] ctrl+D~~
## Creating bindings for a new library ##
To create bindings for a new library, start by creating `.def` file.
Structurally it's a simple property file, looking like this:
header = zlib.h
compilerOpts = -std=c99
linkerOpts = -lz
Then run interop tool with something like (note that for host libraries not included
in sysroot search paths for headers may be needed):
./dist/bin/interop -def:zlib.def -copt:-I/opt/local/include
This command will produce directory named `zlib` containing file `zlib.kt`
and file `zlibstubs.bc` containing implementation specific glue bitcode.
If behavior for certain platform shall be modified, one may use format like
`compilerOpts.osx` or `compilerOpts.linux` to provide platform-specific values
to options.
Note, that generated bindings are generally platform-specific, so if developing for
multiple targets, bindings need to be regenerated.
After generation of bindings they could be used by IDE as proxy view of the
native library.
For typical Unix library with config script `compilerOpts` will likely contain
output of config script with `--cflags` flag (maybe without exact paths) and
`linkerOpts` - output of config script with `--libs`.
Also all those values could be passed directly as values for `-copt` and
`linkedArgs` respectively.
@@ -21,7 +21,7 @@ fun main(args: Array<String>) {
} }
private fun detectHost():String { private fun detectHost():String {
val os =System.getProperty("os.name") val os = System.getProperty("os.name")
when (os) { when (os) {
"Linux" -> return "linux" "Linux" -> return "linux"
"Windows" -> return "win" "Windows" -> return "win"
@@ -53,19 +53,19 @@ private fun substitute(properties: Properties, substitutions: Map<String, String
} }
} }
private fun getArgPrefix(arg: String): String? { private fun getArgPrefix(arg: String): String {
val index = arg.indexOf(':') val index = arg.indexOf(':')
if (index == -1) { if (index == -1) {
return null return ""
} else { } else {
return arg.substring(0, index) return arg.substring(0, index)
} }
} }
private fun dropPrefix(arg: String): String? { private fun dropPrefix(arg: String): String {
val index = arg.indexOf(':') val index = arg.indexOf(':')
if (index == -1) { if (index == -1) {
return null return ""
} else { } else {
return arg.substring(index + 1) return arg.substring(index + 1)
} }
@@ -148,11 +148,25 @@ private fun loadProperties(file: File?, substitutions: Map<String, String>): Pro
return result return result
} }
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
-copt:<c compiler flags> specifies flags passed to clang
-lopt:<linker flags> specifies flags passed to linker
-verbose increases verbosity
-shims adds generation of shims tracing native library calls
-pkg:<fully qualified package name>
-h:<file>.h header files to parse
""")
}
private fun processLib(konanHome: String, private fun processLib(konanHome: String,
substitutions: Map<String, String>, substitutions: Map<String, String>,
commandArgs: List<String>) { commandArgs: List<String>) {
val args = commandArgs.groupBy ({ getArgPrefix(it)!! }, { dropPrefix(it)!! }) // TODO val args = commandArgs.groupBy ({ getArgPrefix(it) }, { dropPrefix(it) }) // TODO
val userDir = System.getProperty("user.dir") val userDir = System.getProperty("user.dir")
val ktGenRoot = args["-generated"]?.single() ?: userDir val ktGenRoot = args["-generated"]?.single() ?: userDir
@@ -162,6 +176,12 @@ private fun processLib(konanHome: String,
val platform = KotlinPlatform.values().single { it.name.equals(platformName, ignoreCase = true) } val platform = KotlinPlatform.values().single { it.name.equals(platformName, ignoreCase = true) }
val defFile = args["-def"]?.single()?.let { File(it) } val defFile = args["-def"]?.single()?.let { File(it) }
if (defFile == null && args["-pkg"] == null) {
usage()
return
}
val config = loadProperties(defFile, substitutions) val config = loadProperties(defFile, substitutions)
val konanFileName = args["-properties"]?.single() ?: val konanFileName = args["-properties"]?.single() ?:
+2 -2
View File
@@ -10,11 +10,11 @@ Then build the compiler:
After that you should be able to compile your programs like that: After that you should be able to compile your programs like that:
./dist/bin/konanc hello.kt -o hello ./dist/bin/kotlinc hello.kt -o hello
For an optimized compilation use -opt: For an optimized compilation use -opt:
./dist/bin/konanc hello.kt -o hello -opt ./dist/bin/kotlinc hello.kt -o hello -opt
For some tests, use: For some tests, use:
+2 -2
View File
@@ -44,9 +44,9 @@ between threads are allowed.
Download Kotlin N redistributable and unpack it. You can run command line compiler with Download Kotlin N redistributable and unpack it. You can run command line compiler with
./dist/bin/kotlinnc <some_file>.kt <dir_with_kt_files> -o <executable>.kexe ./dist/bin/kotlinc <some_file>.kt <dir_with_kt_files> -o <executable>.kexe
One may use `'-h'` flag to `kotlinnc` to see available flags. One may use `'-h'` flag to `kotlinc` to see available flags.
For documentation on C interoperability stubs see INTEROP.md. For documentation on C interoperability stubs see INTEROP.md.
@@ -28,7 +28,7 @@ public class K2NativeCompilerArguments extends CommonCompilerArguments {
@Argument(value = "nolink", description = "Don't link, just produce a bitcode file") @Argument(value = "nolink", description = "Don't link, just produce a bitcode file")
public boolean nolink; public boolean nolink;
@Argument(value = "linkerArg", description = "Add argument to linker") @Argument(value = "linkerArgs", description = "Pass arguments to linker", delimiter = " ")
@ValueDescription("<arg>") @ValueDescription("<arg>")
public String[] linkerArguments; public String[] linkerArguments;
@@ -175,11 +175,26 @@ internal class LinkStage(val context: Context) {
return objectFile return objectFile
} }
fun asLinkerArgs(args: List<String>): List<String> {
val result = mutableListOf<String>()
for (arg in args) {
// If user passes compiler arguments to us - transform them to linker ones.
if (arg.startsWith("-Wl,")) {
result.addAll(arg.substring(4).split(','))
} else {
result.add(arg)
}
}
return result
}
fun link(objectFiles: List<ObjectFile>): ExecutableFile { fun link(objectFiles: List<ObjectFile>): ExecutableFile {
val executable = config.get(KonanConfigKeys.EXECUTABLE_FILE)!! val executable = config.get(KonanConfigKeys.EXECUTABLE_FILE)!!
val linkCommand = platform.linkCommand(objectFiles, executable, optimize) + val linkCommand = platform.linkCommand(objectFiles, executable, optimize) +
config.getNotNull(KonanConfigKeys.LINKER_ARGS) asLinkerArgs(config.getNotNull(KonanConfigKeys.LINKER_ARGS))
println(linkCommand.joinToString("|||"))
runTool(*linkCommand.toTypedArray()) runTool(*linkCommand.toTypedArray())
+2 -3
View File
@@ -31,13 +31,12 @@ while [ $# -gt 0 ]; do
shift shift
;; ;;
*) *)
konan_args=("${konan_args[@]}" "$1") konan_args[${#konan_args[@]}]=$1
shift shift
;; ;;
esac esac
done done
# Based on findScalaHome() from scalac script
findHome() { findHome() {
local source="${BASH_SOURCE[0]}" local source="${BASH_SOURCE[0]}"
while [ -h "$source" ] ; do while [ -h "$source" ] ; do
@@ -64,5 +63,5 @@ JAVA_OPTS=-ea
java_args=("${java_args[@]} -noverify -Dkonan.home=${KONAN_HOME} -Djava.library.path=${NATIVE_LIB}") java_args=("${java_args[@]} -noverify -Dkonan.home=${KONAN_HOME} -Djava.library.path=${NATIVE_LIB}")
$JAVACMD $JAVA_OPTS ${java_args[@]} -cp $KONAN_CLASSPATH $KONAN_COMPILER ${konan_args[@]} $JAVACMD $JAVA_OPTS ${java_args[@]} -cp $KONAN_CLASSPATH $KONAN_COMPILER "${konan_args[@]}"