Saturday, October 30, 2010

Gambit Scheme on Android

After a bit of work and research I've finally made gambit scheme (v4.6) work on android. I had to modify the source code a little bit to make it compile, my changes are as follows:


In lib/os_tty.c

add the following to the list of includes at the top of the file

#include <asm/termios.h>
#include <termios.h>
#include <signal.h>



on line ~790 comment out ctermid (term_name); /* get controlling terminal's name */

change line ~788 to char term_name[L_ctermid] = "/dev/tty";



In lib/os_io.c

add the following to the list of includes at the top of the file


#include <signal.h>
#include <asm/termios.h>
#include <termios.h>




on line ~6438 comment out <stropts.h>



comment out lines ~6503 to ~6509

The lines in question are:


if (!isastream (fd) || 
(ioctl (fd, I_PUSH, "ptem") >= 0 &&
ioctl (fd, I_PUSH, "ldterm") >= 0))
{
*slave_fd = fd;
return 0;
}




In lib/os.c

add #include <asm/signal.h> to the list of includes at the top of the file



In lib/os_time.c

add #include <asm/signal.h> to the list of includes at the top of the file





Now it's ready to compile. I used the crystax ndk to compile, I did have troubled with the official ndk but I'm unsure if another change I did fixed the issue or if it's a general ndk issue. I wrote a small build script to automate the task, the contents as follow:

export ANDROID_ROOT=/home/sean/dev/android-ndk-r4-crystax/build
export PATH=$PATH:$ANDROID_ROOT/prebuilt/linux-x86/arm-eabi-4.4.0/bin/
export LD=arm-eabi-ld
export AR=arm-eabi-ar
export STRIP=arm-eabi-strip
export RANLIB=arm-eabi-ranlib
export CC=arm-eabi-gcc
export CXX=arm-eabi-g++
export PREFIX=$ANDROID_ROOT/../gambc
./configure --enable-single-host --prefix=$PREFIX -host=arm-eabi CC=arm-eabi-gcc CPPFLAGS="-I$ANDROID_ROOT/platforms/android-5/arch-arm/usr/include/ -fno-short-enums" CFLAGS="-fno-short-enums -I$ANDROID_ROOT/platforms/android-5/arch-arm/usr/include/ -nostdlib" LDFLAGS="-fno-short-enums -Wl,-rpath-link=$ANDROID_ROOT/platforms/android-5/arch-arm/usr/lib/ -L$ANDROID_ROOT/platforms/android-5/arch-arm/usr/lib" LIBS="-lc -ldl"
make & make install



You will want to save this to the extracted gambit source directory, I called it build.sh . You will want to edit the paths in this to your liking, specifically PREFIX and ANDROID_ROOT. Now that gambit has been cross compile to android we can make a project.



I started with hello-jni sample project from the official ndk. There are three things that needed to be done; edit the Android.mk file, edit the jni c file and compile the scheme sources to c.



For compiling the scheme sources you just use "gsc -link file.scm" and it will spit out two files, file.c and file_.c. Here is my Android.mk file:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := test
LOCAL_SRC_FILES := test-jni.c fib.c fib_.c
LOCAL_CFLAGS := -I./depends -fno-short-enums
LOCAL_LDLIBS := -ldl -fno-short-enums -lc -L./depends -lgambc

include $(BUILD_SHARED_LIBRARY)



you will want to change the CFLAGS and LDFLAGS to where the gambc include and lib directories are. In the jni folder I made a directory named depends and put the gambit.h and libgambc.a in it.



Here is my code for test-jni.c:


#define ___VERSION 406000

#include "test_test_test.h"
#include
#include
#include
#include "gambit.h"

#define LINKER ____20_fib__

___BEGIN_C_LINKAGE
extern ___mod_or_lnk LINKER (___global_state_struct*);
___END_C_LINKAGE

___setup_params_struct setup_params;

int fib(int x);

jstring Java_test_test_test_stringFromJNI (JNIEnv *env, jobject obj)
{
// Taken from gambit, lib/main.c.
int debug_settings = ___DEBUG_SETTINGS_INITIAL;

// -:d- (force repl io to be stdin/stdout since terminal isn't
// -attached)
debug_settings =
(debug_settings
& ~___DEBUG_SETTINGS_REPL_MASK)
| (___DEBUG_SETTINGS_REPL_STDIO
<< ___DEBUG_SETTINGS_REPL_SHIFT);
// -:da
debug_settings =
(debug_settings
& ~___DEBUG_SETTINGS_UNCAUGHT_MASK)
| (___DEBUG_SETTINGS_UNCAUGHT_ALL
<< ___DEBUG_SETTINGS_UNCAUGHT_SHIFT);
// -:dr
debug_settings =
(debug_settings
& ~___DEBUG_SETTINGS_ERROR_MASK)
| (___DEBUG_SETTINGS_ERROR_REPL
<< ___DEBUG_SETTINGS_ERROR_SHIFT);

___setup_params_reset (&setup_params);
setup_params.version = ___VERSION;
setup_params.linker = LINKER;
setup_params.debug_settings = debug_settings;

___setup(&setup_params);

char buffer[100];
int n = sprintf(buffer, "fib of 10 is: %d", fib(10));

return (*env)->NewStringUTF(env, buffer);
}



You will notice there is quite a bit extra in there, which is required for calling scheme functions from c without having scheme as the main entry point. The one thing that will have to change if you have a different scheme file is LINKER ____20_fib__, you will need to replace fib with the name of the main file you are compiling to scheme.



Now we can just build ndk library with ndk-build and then build the java portion and run. All my example does is output the string "fib of 10 is: 55".



For clarity I've upload a sample project to github. It also includes the precompiled libgambc.a so you won't need to recompile it yourself.



I'm hoping that this will be a nice way to develop games in scheme on my desktop and then port them to android. Anyways, have fun.