1. Before we start

$JAVA_HOME must be pointing to the correct location

Kotlin must be installed, and kotlinc (Kotlin compiler) must be in $PATH

The commands below are written to work in macOS. Unless mentioned, the command should work in Linux by changing macosx to linux, and the architecture (if needed.) For example macosx-x86_64 would become linux-i386 or linux-x86_64

This tutorial is based on the README of Avian. Most of my work was to make it work with Kotlin instead of Java, and to put it in groups of instructions that makes sense for people new to Avian along with explanations of what we are doing.

2. Getting and building Avian

Let’s start by getting the latest version of Avian:

Then build it with the default configuration, and test that it’s working

$ cd avian

$ make

$ build/macosx-x86_64/avian -cp build/macosx-x86_64/test Hello

The last command should print “hello, world!” if everything is correct.

3. Writing our simple Kotlin program, and packing it

Let’s create a folder, put our little Kotlin script there, and pack it into a jar.

$ cd ../

$ mkdir helloKotlin && cd helloKotlin

$ cat >Hello.kt <<EOF

fun main(args: Array<String>) {

println("Hello from Kotlin and Avian")

println("args: ")

args.forEach {

println("-\$it")

}

}

EOF

$ kotlinc Hello.kt -include-runtime -d boot.jar

Notice that we are compiling including Kotlin’s runtime, that means that if you run unzip -l boot.jar you will see all Kotlin’s classes in there. This is important since we want a stand-alone application.

4. Preparing Avian’s runtime to be merged with Kotlin’s

We are going to get Avian’s runtime and extract it, and also get the files needed to create a binary with Avian.

$ ar x ../avian/build/macosx-x86_64/libavian.a

$ mkdir avian-cp

$ cp ../avian/build/macosx-x86_64/classpath.jar avian-cp/avian-cp.jar

$ cd avian-cp/

$ unzip avian-cp.jar && rm -rf avian-cp.jar

5. Merging Avian, Kotlin, and our app in one jar

Here we create one jar that has Avian’s runtime, Kotlin’s runtime, and our little application. We’ll just merge all the content that we extracted before from avian-cp.jar into boot.jar (which already has Kotlin and our code.)

$ mv ../boot.jar .

$ zip -r boot.jar META-INF avian dalvik java libcore sun

$ mv boot.jar ../ && cd ../

If you run unzip -l boot.jar now, you will see that all the classes are now happy together.

6. Creating a binary

Now we have a self-contained jar. Left is only to use Avian to create a binary out of the jar. First we create an object from the jar.

$ ../avian/build/macosx-x86_64/binaryToObject/binaryToObject boot.jar \boot-jar.o _binary_boot_jar_start _binary_boot_jar_end macosx x86_64

The following command will create the c++ main class for our binary. Please notice that FindClass is looking for HelloKt, since the classes compiled with kotlinc wil have a Kt suffix (javac compiles Hello.java into Hello.class, whereas kotlinc compiles Hello.kt into HelloKt.class.)

$ cat >embedded-jar-main.cpp <<EOF

#include "stdint.h"

#include "jni.h"

#include "stdlib.h" #if (defined __MINGW32__) || (defined _MSC_VER)

# define EXPORT __declspec(dllexport)

#else

# define EXPORT __attribute__ ((visibility("default"))) \

__attribute__ ((used))

#endif #if (! defined __x86_64__) && ((defined __MINGW32__) || (defined _MSC_VER))

# define SYMBOL(x) binary_boot_jar_##x

#else

# define SYMBOL(x) _binary_boot_jar_##x

#endif extern "C" { extern const uint8_t SYMBOL(start)[];

extern const uint8_t SYMBOL(end)[]; EXPORT const uint8_t*

bootJar(size_t* size)

{

*size = SYMBOL(end) - SYMBOL(start);

return SYMBOL(start);

} } // extern "C" extern "C" void __cxa_pure_virtual(void) { abort(); } int

main(int ac, const char** av)

{

JavaVMInitArgs vmArgs;

vmArgs.version = JNI_VERSION_1_2;

vmArgs.nOptions = 1;

vmArgs.ignoreUnrecognized = JNI_TRUE; JavaVMOption options[vmArgs.nOptions];

vmArgs.options = options; options[0].optionString = const_cast<char*>("-Xbootclasspath:[bootJar]"); JavaVM* vm;

void* env;

JNI_CreateJavaVM(&vm, &env, &vmArgs);

JNIEnv* e = static_cast<JNIEnv*>(env); jclass c = e->FindClass("HelloKt");

if (not e->ExceptionCheck()) {

jmethodID m = e->GetStaticMethodID(c, "main", "([Ljava/lang/String;)V");

if (not e->ExceptionCheck()) {

jclass stringClass = e->FindClass("java/lang/String");

if (not e->ExceptionCheck()) {

jobjectArray a = e->NewObjectArray(ac-1, stringClass, 0);

if (not e->ExceptionCheck()) {

for (int i = 1; i < ac; ++i) {

e->SetObjectArrayElement(a, i-1, e->NewStringUTF(av[i]));

} e->CallStaticVoidMethod(c, m, a);

}

}

}

} int exitCode = 0;

if (e->ExceptionCheck()) {

exitCode = -1;

e->ExceptionDescribe();

} vm->DestroyJavaVM(); return exitCode;

}

EOF

Now we will compile the C++ class into an object, and link all the objects (notice that in step 4 we copied a bunch of Avian’s objects into the current folder) to finally create our binary, that will be called hello.

For macOS:

$ g++ -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin \

-D_JNI_IMPLEMENTATION_ -c embedded-jar-main.cpp -o main.o

$ g++ -rdynamic *.o -ldl -lpthread -lz -o hello -framework CoreFoundation

$ strip -S -x hello

For Linux:

$ g++ -I$JAVA_HOME/include -I$JAVA_HOME/include/linux \

-D_JNI_IMPLEMENTATION_ -c embedded-jar-main.cpp -o main.o

$ g++ -rdynamic *.o -ldl -lpthread -lz -o hello

$ strip --strip-all hello

7. Run it

Now we can finally run it and see how it works