Improve interop user experience and docs (#342)
This commit is contained in:
+56
-2
@@ -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).
|
||||
|
||||
@@ -8,7 +27,7 @@ Prepare stubs for the system sockets library:
|
||||
|
||||
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
|
||||
|
||||
Run the server:
|
||||
@@ -23,3 +42,38 @@ Write something to console and watch server echoing it back.
|
||||
|
||||
~~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.
|
||||
|
||||
+26
-6
@@ -21,7 +21,7 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
private fun detectHost():String {
|
||||
val os =System.getProperty("os.name")
|
||||
val os = System.getProperty("os.name")
|
||||
when (os) {
|
||||
"Linux" -> return "linux"
|
||||
"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(':')
|
||||
if (index == -1) {
|
||||
return null
|
||||
return ""
|
||||
} else {
|
||||
return arg.substring(0, index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dropPrefix(arg: String): String? {
|
||||
private fun dropPrefix(arg: String): String {
|
||||
val index = arg.indexOf(':')
|
||||
if (index == -1) {
|
||||
return null
|
||||
return ""
|
||||
} else {
|
||||
return arg.substring(index + 1)
|
||||
}
|
||||
@@ -148,11 +148,25 @@ private fun loadProperties(file: File?, substitutions: Map<String, String>): Pro
|
||||
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,
|
||||
substitutions: Map<String, 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 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 defFile = args["-def"]?.single()?.let { File(it) }
|
||||
|
||||
if (defFile == null && args["-pkg"] == null) {
|
||||
usage()
|
||||
return
|
||||
}
|
||||
|
||||
val config = loadProperties(defFile, substitutions)
|
||||
|
||||
val konanFileName = args["-properties"]?.single() ?:
|
||||
|
||||
@@ -10,11 +10,11 @@ Then build the compiler:
|
||||
|
||||
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:
|
||||
|
||||
./dist/bin/konanc hello.kt -o hello -opt
|
||||
./dist/bin/kotlinc hello.kt -o hello -opt
|
||||
|
||||
For some tests, use:
|
||||
|
||||
|
||||
+2
-2
@@ -44,9 +44,9 @@ between threads are allowed.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ public class K2NativeCompilerArguments extends CommonCompilerArguments {
|
||||
@Argument(value = "nolink", description = "Don't link, just produce a bitcode file")
|
||||
public boolean nolink;
|
||||
|
||||
@Argument(value = "linkerArg", description = "Add argument to linker")
|
||||
@Argument(value = "linkerArgs", description = "Pass arguments to linker", delimiter = " ")
|
||||
@ValueDescription("<arg>")
|
||||
public String[] linkerArguments;
|
||||
|
||||
|
||||
+16
-1
@@ -175,11 +175,26 @@ internal class LinkStage(val context: Context) {
|
||||
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 {
|
||||
val executable = config.get(KonanConfigKeys.EXECUTABLE_FILE)!!
|
||||
|
||||
val linkCommand = platform.linkCommand(objectFiles, executable, optimize) +
|
||||
config.getNotNull(KonanConfigKeys.LINKER_ARGS)
|
||||
asLinkerArgs(config.getNotNull(KonanConfigKeys.LINKER_ARGS))
|
||||
|
||||
println(linkCommand.joinToString("|||"))
|
||||
|
||||
runTool(*linkCommand.toTypedArray())
|
||||
|
||||
|
||||
+2
-3
@@ -31,13 +31,12 @@ while [ $# -gt 0 ]; do
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
konan_args=("${konan_args[@]}" "$1")
|
||||
konan_args[${#konan_args[@]}]=$1
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Based on findScalaHome() from scalac script
|
||||
findHome() {
|
||||
local source="${BASH_SOURCE[0]}"
|
||||
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}")
|
||||
|
||||
$JAVACMD $JAVA_OPTS ${java_args[@]} -cp $KONAN_CLASSPATH $KONAN_COMPILER ${konan_args[@]}
|
||||
$JAVACMD $JAVA_OPTS ${java_args[@]} -cp $KONAN_CLASSPATH $KONAN_COMPILER "${konan_args[@]}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user