After a bit of fiddling, I managed to get a native binary executable file, cross-compiled from Nim source code, which runs on my Android phone running Android 8.0 (Oreo).
Figured someone would find it useful to see how I did it, although there's probably a cleaner way to do it than what I ended up doing. This was rather just thrown together in a way that I found worked, so bear that in mind. x')
Hopefully this can also help the Nim developers make cross-compiling a Nim program for Android easier to do. ;)
I'll also note that I did this from a Windows x64 machine.
Disclaimer: You follow these instructions at your own risk, etc... Not my fault if you brick your device fiddling with it.
make_standalone_toolchain.py --arch=arm64 --install-dir=../../../android-toolchain --api 27
This will have created the android-toolchain directory in the same location where you put the NDK.
Note that the number given for api is the minimum Android API level that your app will target.
nim c --os:android --cpu=arm64 --compileOnly hello.nim
clang -I../sysroot/usr/include -I/path/to/nim/installation/lib -fPIE -pie -o /path/to/output/hello.bin path/to/nimcache/hello.c /path/to/nimcache/stdlib_system.c
This generated 19 warnings for me, but it still ran just fine in the end. Note that I've specified all the C files manually here, but you could do *.c if you ensure that you clean the nimcache every time.
I am compiling manually via C here because Nim calls out to clang.exe or gcc.exe and cannot be told to call the clang.cmd as we need to here, so far as I could tell.
I tried using a config file like 'hello.nim.cfg' to config this but I was not able to get that to work.
At any rate, you should now have an executable binary file that runs on Android ARM64.
Unfortunately, I can only tell you how to run it on real hardware. You can do that like this:
adb push hello.bin /data/local/tmp/hello.bin
adb shell
$ chmod +x /data/local/tmp/hello.bin
$ /data/local/tmp/hello.bin
Hello World!
$
If your program segfaults but you know that it's not your Nim code's fault, try removing the '-fPIE -pie' from the clang complication step; particularly if you have a very old version of Android. Position independant code was not originally supported, IIRC.
Hopefully there will be an easier, cleaner, way to do this in the future! xD
Thank you, I did different approach. I followed from this stackoverflow thread about cross compiling https://stackoverflow.com/questions/16795583/android-error-cannot-open-crtbegin-dynamic-o-no-such-file-or-directory
I was stuck because I added sysroot option to the compiler when compiling C and after I looked your example, I realized I need to add --compileOnly to Nim compiler and do compiling as usual
For completeness, this is what I did
> set PATH=%PATH%;/my-installed-ndk/toolchains/arm-linux-android-eabi-4.9/prebuilt/windows-x86_64/bin
> nim --cpu:arm --os:android --compileOnly hello.nim
> cd nimcache
> arm-linux-androideabi-gcc.exe -I my-installed-nim-folder/lib hello.c stdlib_system.c -o hello --sysroot=my-installed-ndk/platforms/android-19/arch-arm
> adb root # starting adb server, make sure adb.exe in PATH
> adb push hello /data/local/tmp
> adb shell
$ su
# cd /data/local/tmp
# chmod 755 hello
# ./hello
cross compiled hello
I cross-compiled from Windows 10 64 bit too and targeting android-19 (4.4)
Hi, :)
I tried last example. Unfortunately doesn't work for me.
C:\Users\uname\Documents\nimapps\jnijava\nimcache>arm-linux-androideabi-gcc.exe -I C:\Nim\lib hello.c stdlib_system.c -o hello --sysroot=C:\Users\uname\Documents\apps\android-ndk-r16b\platforms\android-19\arch-arm In file included from c:\users\uname\documents\apps\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\lib\gcc\arm-linux-androideabi\4.9.x\include-fixed\syslimits.h:7:0, from c:\users\uname\documents\apps\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\lib\gcc\arm-linux-androideabi\4.9.x\include-fixed\limits.h:34, from C:\Nim\lib/nimbase.h:255, from hello.c:7: c:\users\uname\documents\apps\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\lib\gcc\arm-linux-androideabi\4.9.x\include-fixed\limits.h:168:61: error: no include path in which to search for limits.h #include_next <limits.h> /* recurse down to the real one */ ^ In file included from c:\users\uname\documents\apps\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\lib\gcc\arm-linux-androideabi\4.9.x\include-fixed\syslimits.h:7:0, from c:\users\uname\documents\apps\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\lib\gcc\arm-linux-androideabi\4.9.x\include-fixed\limits.h:34, from C:\Nim\lib/nimbase.h:255, from stdlib_system.c:7: c:\users\uname\documents\apps\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\lib\gcc\arm-linux-androideabi\4.9.x\include-fixed\limits.h:168:61: error: no include path in which to search for limits.h #include_next <limits.h> /* recurse down to the real one */ ^ stdlib_system.c:8:20: fatal error: setjmp.h: No such file or directory #include <setjmp.h> ^ compilation terminated.
What I do wrong? Could you please help...
Have you tried / in path string instead of \ as path separator ?
Also you can try adding ".
Very likely because you were using \ so gcc couldn't find the correct path. See my example above all using /.
Hi mashingan,
thank you for suggestion and I tried all this /," and // possibilities but errors are the same.
any other suggestions? maybe I have not prepared some things...
Could you give the command-line you typed?
For example, what I did was like this
arm-linux-androideabi-gcc.exe -I "C:/Nim/lib" hello.c stdlib_system.c -o hello --sysroot="C:/Users/uname/Documents/apps/android-ndk-r16b/platforms/android-19/arch-arm"
> cd C:\Users\uname\Documents\nimapps\jnijava\ > set PATH=%PATH%;C:\Users\uname\Documents\apps\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin > nim c --cpu:arm --os:android --compileOnly hello.nim > cd nimcache > arm-linux-androideabi-gcc.exe -I "C:/Nim/lib" hello.c stdlib_system.c -o hello --sysroot="C:/Users/uname/Documents/apps/android-ndk-r16b/platforms/android-19/arch-arm"
:)
Hi mashingan,
I tried cross-compile the same hello.nim from linux and got the same errors exactly like in windows.
what I did:
Hmm, I'm not sure, all I can say is the cross compiler cannot find the sysroot
This the exact command line I did and it success
\nimcache>arm-linux-androideabi-gcc -I d:/installer/nim/lib hello_android.c stdlib_system.c -o hello --sysroot=e:/installer/android/ndk/platforms/android-19/arch-arm
Before I did failed like what you had but because I wrote the sysroot wrong, before I wrote it --sysroot=e:/installer/ndk/platforms/android-19/arch-arm , but after recompiled with correct path, it compiled successfully.
My Nim version is still 0.17.2, 64 bit (haven't had a chance to try 0.18.0)
My arm-linux-androideabi-gcc version and its path dependency
arm-linux-androideabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-linux-androideabi-gcc
COLLECT_LTO_WRAPPER=e:/installer/android/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/../libexec/gcc/arm-linux-androideabi/4.9.x/lto-wrapper.exe
Target: arm-linux-androideabi
Configured with: /usr/local/google/buildbot/src/android/gcc/toolchain/build/../gcc/gcc-4.9/configure --prefix=/tmp/54f254f8bb7b48dd787052921220a87d --target=arm-linux-androideabi --host=x86_64-pc-mingw32msvc --build=x86_64-linux-gnu --with-gnu-as --with-gnu-ld --enable-languages=c,c++ --with-gmp=/buildbot/tmp/build/toolchain/temp-install --with-mpfr=/buildbot/tmp/build/toolchain/temp-install --with-mpc=/buildbot/tmp/build/toolchain/temp-install --with-cloog=/buildbot/tmp/build/toolchain/temp-install --with-isl=/buildbot/tmp/build/toolchain/temp-install --with-ppl=/buildbot/tmp/build/toolchain/temp-install --disable-ppl-version-check --disable-cloog-version-check --disable-isl-version-check --enable-cloog-backend=isl --with-host-libstdcxx='-static-libgcc -static-libstdc++ -lstdc++ -lm -static' --disable-libssp --enable-threads --disable-nls --disable-libmudflap --disable-libgomp --disable-libstdc__-v3 --disable-sjlj-exceptions --disable-shared --disable-tls --disable-libitm --with-float=soft --with-fpu=vfp --with-arch=armv5te --enable-target-optspace --enable-bionic-libs --enable-libatomic-ifuncs=no --enable-initfini-array --disable-nls --prefix=/tmp/54f254f8bb7b48dd787052921220a87d --with-sysroot=/tmp/54f254f8bb7b48dd787052921220a87d/sysroot --with-binutils-version=2.25 --with-mpfr-version=3.1.1 --with-mpc-version=1.0.1 --with-gmp-version=5.0.5 --with-gcc-version=4.9 --with-gdb-version=none --with-gxx-include-dir=/tmp/54f254f8bb7b48dd787052921220a87d/include/c++/4.9.x --with-bugurl=http://source.android.com/source/report-bugs.html --enable-languages=c,c++ --disable-bootstrap --enable-plugins --enable-libgomp --enable-gnu-indirect-function --disable-libsanitizer --enable-gold --enable-eh-frame-hdr-for-static --enable-graphite=yes --with-isl-version=0.11.1 --with-cloog-version=0.18.0 --with-arch=armv5te --program-transform-name='s&^&arm-linux-androideabi-&' --enable-gold=default
Thread model: posix
gcc version 4.9.x 20150123 (prerelease) (GCC)
My system is Windows 10 64 bit
Hi
finelly is works!
I tried the way suggested Tetralux via make_standalone_toolchain.py and then clang.
Simple echo works, but I tried simple pow from math and clang doesn't compile it:
import math
var a = pow(5.09, 2.33)
clang -I ../sysroot/usr/include -I C:/Nim/lib -fPIE -pie -o C:/Users/uname/Documents/nimapps/jnijava/hello.bin C:/Users/uname/Documents/nimapps/jnijava/nimcache/*.c
...and get error:
\nimcache\hello.c:function NimMainModule: error: undefined reference to 'pow' clang50.exe: error: linker command failed with exit code 1 (use -v to see invocation)
thanks a lot mashingan! It is working!
Now I am going to implement JNI to call java classes(some SDK has been already developed in Java) for manage android application.
@yglukhov, thanks a lot. It is nice. :)
now I can some trouble: I can't implement some listeners
for example after the defination a Button:
import jnim
import android.widget.textview
import android / content / [ context ]
import android.view.on_click_listener
jclass android.widget.Button* of TextView:
proc new*(ctx: Context)
proc setText*(text: string)
proc setOnClickListener*(onClick: OnClickListener)
... and Button object is working properly in my android application but I can't implement OnClickListener for this button
additional created on_click_listener.nim:
import jnim
import android.view.View
jclass android.view.OnClickListener* of JVMObject:
proc onClick*(v: View)
...and I don't know what do next?
@yglukhov, thanks, it was helpful. For most cases the functionality of java interface implementation works.
But in some cases I get error at runtime:
java_vm_ext.cc:504] JNI DETECTED ERROR IN APPLICATION: JNI NewGlobalRef called with pending exception java.lang.NullPointerException: Expected to unbox a 'boolean' primitive type but was returned null
let prGstr = makeProxy(SomeListenerInterface, mr, MyObj_dispatcher)
mGstr.addOnSomeListener(prGstr, 10, true.jboolean) #<-here runtime exception
and I have another question: I tried to implement this:
import asyncdispatch
proc callAsync() {.async.} =
await sleepAsync(1)
android_log_write(4, "TEST", "async wait")
discard callAsync()
waitFor callAsync()
this works as expected I get two "async wait" in the log. But if I set comment for waitFor callAsync() like this:
import asyncdispatch
proc callAsync() {.async.} =
await sleepAsync(1)
android_log_write(4, "TEST", "async wait")
discard callAsync()
#waitFor callAsync()
or if run the discard callAsync() in callback of events then nothing is coming to the log. But I think it is nothing to do with JNI on android?
thanks
Hi @yglukhov,
the issue with JNI DETECTED ERROR IN APPLICATION: JNI NewGlobalRef........ resolved to using ByteBuddy https://github.com/raphw/byte-buddy/tree/master/byte-buddy-android because it is faster instead of Java Reflection classes
But I get sometimes other crash:
or in call NewGlobalRef, DeleteGlobalRef etc.
Partially I resolved it by using jniCall theVM.AttachCurrentThread(theVM, castptr pointer, args.addr), "Error attaching thread to VM"
but it doesn't help always... :(
any ideas?