Search code examples
androidkotlingoarchitectureswig

Compile libraries for Kotlin android,rust and go


This question is exploratory in nature, not sure if this fits stack overlflow Q&A.

Context:

I have a library written in golang that I need to compile for multiple services to use.

These services are in Kotlin android,Rust,Golang.

The only option I am aware of is using something like SWIG to compile the go library for different languages.

Problem:

  1. I don't think SWIG works for Kotlin.

I am trying to fish for the best methods to do this and different approaches this can be done.


Solution

  • For any language that can generate a C shared library and header file you can use SWIG to wrap it. Equally for any language that runs within a JVM and can call Java classes you can make use of SWIG's auto generated Java bindings.

    With that we can therefore do a sequence of things that looks like this:

    Go -> C -> JNI -> Java -> Kotlin

    It's actually fairly sane. I've put together an example below for this to show how it works since I was curious having never written Go nor Kotlin before. (Take this with a pinch of salt therefore, I've probably not hit "best practice" for either!)

    This example assumes you have a working JDK/JRE, C compiler, Go installation and kotlinc.

    My demo.go looks like this:

    package main
    
    import (
        "C"
        "fmt"
    )
    
    //export TestGoFunc
    func TestGoFunc(str *C.char) *C.char {
            fmt.Printf("Got string: %s\n", C.GoString(str))
            return nil
    }
    
    func main() {}
    

    And hello.kt looks like this:

    fun main() {
        println("Hello, World!")
        test.TestGoFunc("Another string")
    }
    

    To wrap this I wrote the following SWIG interface:

    %module test
    
    %{
    #include "golib.h"
    %}
    
    %include <typemaps.i>
    
    %pragma(java) jniclasscode=%{
      static {
        System.loadLibrary("test");
      }
    %}
    
    // Assuming you don't care about these in your library silence/neaten stuff
    #define _Complex
    %ignore _GoString_;
    %ignore GoComplex64;
    %ignore GoComplex128;
    %ignore GoSlice;
    %ignore GoInterface;
    
    %include "golib.h"
    

    This is a fairly standard SWIG interface for targeting Java - it hides some of the stuff in the generated header file we don't care about and autoloads the .so file inside Java using a pragma for us.

    Then I put together a small Makefile to build everything since there's a bunch of steps to this build:

    all: libtest.so hello.jar
    
    golib.so: demo.go
            go build -o golib.so -buildmode=c-shared demo.go
    
    test_wrap.c: golib.so test.i
            swig3.0 -java -Wall test.i
    
    libtest.so: test_wrap.c
            gcc -shared -Wall -Wextra test_wrap.c -o libtest.so ./golib.so -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux
    
    hello.jar: hello.kt
            javac *.java
            kotlinc hello.kt -include-runtime -d hello.jar -cp .
            jar uvf hello.jar *.class
    

    If we build and run this then it all works nicely:

    $ make
    go build -o golib.so -buildmode=c-shared demo.go
    swig3.0 -java -Wall test.i
    gcc -shared -Wall -Wextra test_wrap.c -o libtest.so ./golib.so -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux
    javac *.java
    kotlinc hello.kt -include-runtime -d hello.jar -cp .
    jar uvf hello.jar *.class
    adding: test.class(in = 302) (out= 216)(deflated 28%)
    adding: testJNI.class(in = 389) (out= 268)(deflated 31%)
    $ LD_LIBRARY_PATH=. java -jar hello.jar
    Hello, World!
    Got string: Another string
    

    I'd be tempted to use -buildmode=c-archive for Go to build a static library and then link that into the SWIG shared object instead just to keep things simpler in that regards though.