Commits

keigoi  committed 3f8b2c3

first commit

  • Participants

Comments (0)

Files changed (14)

File AndroidManifest.xml

+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="jp.co.itpl.ocamlandroid"
+      android:versionCode="1"
+      android:versionName="1.0">
+    <application android:icon="@drawable/icon" android:label="@string/app_name">
+        <activity android:name=".MainActivity"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-sdk android:minSdkVersion="3" />
+
+
+</manifest> 

File default.properties

+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+# 
+# This file must be checked in Version Control Systems.
+# 
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-3

File gen/jp/co/itpl/ocamlandroid/R.java

+/* AUTO-GENERATED FILE.  DO NOT MODIFY.
+ *
+ * This class was automatically generated by the
+ * aapt tool from the resource data it found.  It
+ * should not be modified by hand.
+ */
+
+package jp.co.itpl.ocamlandroid;
+
+public final class R {
+    public static final class attr {
+    }
+    public static final class drawable {
+        public static final int icon=0x7f020000;
+    }
+    public static final class layout {
+        public static final int main=0x7f030000;
+    }
+    public static final class string {
+        public static final int app_name=0x7f040001;
+        public static final int hello=0x7f040000;
+    }
+}

File jni/Android.mk

+# EDIT BELOW
+OCAMLANDROID_PREFIX=/Users/keigoi/usr/android-ocaml
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_MODULE    := ocamltop
+LOCAL_SRC_FILES := libocamltop.c start.c
+LOCAL_CFLAGS    := -std=c99 -I$(OCAMLANDROID_PREFIX)/lib/ocaml 
+LOCAL_STATIC_LIBRARIES    := libcamlrun
+
+$(NDK_APP_PROJECT_PATH)/obj/local/armeabi/libcamlrun.a:
+	mkdir -p obj/local/armeabi/
+	cp $(OCAMLANDROID_PREFIX)/lib/ocaml/libcamlrun.a obj/local/armeabi/
+
+include $(BUILD_SHARED_LIBRARY)

File jni/README.txt

+- JNI用ヘッダファイルの生成
+  javah -classpath bin jp.co.itpl.ocamlandroid.Native
+
+- ocamlトップレベルのCコード生成
+  1. OCaml on Androidをビルド後、Makefileに次の2行を加える
+
+ocaml_toplevel_lib: $(TOPOBJS)
+	$(CAMLC) $(LINKFLAGS) -linkall -output-obj -o ocaml.c $(TOPOBJS)
+
+  (タブに注意)
+
+  2. make ocaml_toplevel_lib を実行する
+

File jni/jp_co_itpl_ocamlandroid_Native.h

+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class jp_co_itpl_ocamlandroid_Native */
+
+#ifndef _Included_jp_co_itpl_ocamlandroid_Native
+#define _Included_jp_co_itpl_ocamlandroid_Native
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     jp_co_itpl_ocamlandroid_Native
+ * Method:    start
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_jp_co_itpl_ocamlandroid_Native_start
+  (JNIEnv *, jclass, jstring);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+#include "jp_co_itpl_ocamlandroid_Native.h"
+#include "caml/callback.h"
+
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define SERVER     "jp.co.itpl.ocamltop.stdinout"
+#define SERVER_ERR "jp.co.itpl.ocamltop.stderr"
+
+/*
+ * Create a UNIX-domain socket address in the Linux "abstract namespace".
+ *
+ * The socket code doesn't require null termination on the filename, but
+ * we do it anyway so string functions work.
+ */
+static int makeAddr(const char* name, struct sockaddr_un* pAddr, socklen_t* pSockLen)
+{
+    int nameLen = strlen(name);
+    if (nameLen >= (int) sizeof(pAddr->sun_path) -1)  /* too long? */
+        return -1;
+    pAddr->sun_path[0] = '\0';  /* abstract namespace */
+    strcpy(pAddr->sun_path+1, name);
+    pAddr->sun_family = AF_LOCAL;
+    *pSockLen = 1 + nameLen + offsetof(struct sockaddr_un, sun_path);
+    return 0;
+}
+
+static int waitfor(const char* name) {
+
+  struct sockaddr_un server_addr;
+  socklen_t server_addr_len;
+
+  if (makeAddr(name, &server_addr, &server_addr_len) < 0)
+      return 0;
+
+  int listenfd = socket(AF_LOCAL, SOCK_STREAM, PF_UNIX);
+  if (listenfd < 0) {
+      return 0;
+  }
+
+  int serverfd = 0;
+  if (bind(listenfd, (const struct sockaddr*) &server_addr, server_addr_len) < 0) {
+      perror("server bind()");
+      goto bail;
+  }
+  if (listen(listenfd, 5) < 0) {
+      perror("server listen()");
+      goto bail;
+  }
+  serverfd = accept(listenfd, NULL, NULL);
+  if (serverfd < 0) {
+      perror("server accept");
+      serverfd = 0;
+      goto bail;
+  }
+
+bail:
+  if(listenfd>0) close(listenfd);
+
+  return serverfd;
+}
+
+static int server_inout = 0;
+static int server_err = 0;
+
+static int redirect_start() {
+
+  server_err = waitfor(SERVER_ERR);
+  if(server_err==0) return 0;
+  server_inout = waitfor(SERVER);
+  if(server_inout==0) return 0;
+
+  dup2(server_inout, 0); // stdin  
+  dup2(server_inout, 1); // stdout
+  dup2(server_err, 2); // stderr
+
+  return 1;
+}
+
+static void redirect_end() {
+  // TODO dup2で変えたfdを元に戻す
+  if(server_err!=0) close(server_inout);
+  if(server_inout!=0) close(server_inout);
+}
+
+static void chdir_jstring(JNIEnv * env, jstring path_) {
+  const jbyte *path = (*env)->GetStringUTFChars(env, path_, NULL);
+  if (path == NULL) {
+      return;
+  }
+  chdir(path);
+  (*env)->ReleaseStringUTFChars(env, path_, path);
+}
+
+JNIEXPORT void JNICALL Java_jp_co_itpl_ocamlandroid_Native_start (JNIEnv * env, jclass clazz, jstring path) {
+
+  chdir_jstring(env, path);
+  
+  if(!redirect_start()) {
+    redirect_end(); 
+    return;
+  }
+
+  char* p = 0;
+  caml_startup(&p);
+
+  redirect_end();
+  
+}

File libs/commons-io-2.0.jar

Binary file added.

File res/drawable/icon.png

Added
New image

File res/layout/main.xml

+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+<TextView  
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content" 
+    android:text="@string/hello"
+    />
+</LinearLayout>

File res/values/strings.xml

+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello World, MainActivity!</string>
+    <string name="app_name">ocaml-android</string>
+</resources>

File src/jp/co/itpl/ocamlandroid/MainActivity.java

+package jp.co.itpl.ocamlandroid;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class MainActivity extends Activity {
+	public static final String TAG = "ocaml-android";
+
+	private static void outForever(final InputStream raw) {
+		new Thread() {
+			public void run() {
+				try {
+			    	String line = null;
+			    	BufferedReader in = new BufferedReader(new InputStreamReader(raw, "utf-8"));
+			    	while((line=in.readLine())!=null) {
+			    		Log.i(TAG, line);
+			    	}
+		        } catch(IOException e) {
+		        	Log.e(TAG, "reading stdout/stderr error", e);
+		        }
+			}
+		}.start();
+	}
+	
+	@Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);        
+
+        try {
+            OcamlTop ocaml = new OcamlTop(this);
+	    	outForever(ocaml.stdinout.getInputStream());
+	    	outForever(ocaml.stderr.getInputStream());
+	    	
+	    	PrintWriter out = new PrintWriter(new OutputStreamWriter(ocaml.stdinout.getOutputStream(), "utf-8"));
+	    	out.println("1;;\n");
+	    	out.flush();
+        } catch(IOException e) {
+        	Log.e(TAG, "error in main", e);
+        }
+
+   }
+}

File src/jp/co/itpl/ocamlandroid/Native.java

+package jp.co.itpl.ocamlandroid;
+
+public class Native {
+
+	static {
+		System.loadLibrary("ocamltop");
+	}
+
+	public static native void start(String name);
+	
+}

File src/jp/co/itpl/ocamlandroid/OcamlTop.java

+package jp.co.itpl.ocamlandroid;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.io.IOUtils;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.util.Log;
+
+public class OcamlTop {
+	public static final String TAG = "ocaml-android";
+	public static final String ASSETS_STDLIBPATH = "ocaml-stdlib";
+	public static final String STDINOUT = "jp.co.itpl.ocamltop.stdinout";
+	public static final String STDERR = "jp.co.itpl.ocamltop.stderr";
+
+	private void prepareStdlib(Context context) {
+		File cache = context.getCacheDir();
+		AssetManager assets = context.getAssets();
+		try {
+			String[] files = assets.list(ASSETS_STDLIBPATH);
+			Log.d(TAG,"copying "+files.length+" files of stdlib..");
+			for(String file : files) {
+				InputStream in = null;
+				OutputStream out = null;
+				try {
+					in = assets.open(ASSETS_STDLIBPATH+File.separator+file);
+					String outfile = cache.getAbsolutePath()+File.separator+file;
+					out = new FileOutputStream(outfile);
+					IOUtils.copy(in, out);
+				} finally {
+					IOUtils.closeQuietly(in);
+					IOUtils.closeQuietly(out);					
+				}
+			}
+			Log.d(TAG,"copy complete.");
+		} catch(IOException e) {
+			Log.e(TAG, "stdlib preparation error", e);
+			// FIXME: exit
+		}
+	}
+	
+	private static void sleep(long millis) {
+		try{ Thread.sleep(millis); } catch(InterruptedException e) { Thread.currentThread().interrupt(); }
+	}
+	
+	
+    public final LocalSocket stdinout = new LocalSocket();
+    public final LocalSocket stderr = new LocalSocket();
+
+    public OcamlTop(final Context context) throws IOException {
+    	// FIXME: stdin/out, stderr の接続を個別の関数に分ければsleepは不要になる
+        new Thread(){ public void run() { Native.start(context.getCacheDir().getAbsolutePath()); } }.start();
+    	Log.i(TAG, "thread started");
+        
+        prepareStdlib(context);
+
+    	Log.i(TAG, "connecting");
+    	sleep(500);
+    	stderr.connect(new LocalSocketAddress(STDERR, LocalSocketAddress.Namespace.ABSTRACT));
+    	Log.i(TAG, "stderr connected");
+    	sleep(500);
+    	stdinout.connect(new LocalSocketAddress(STDINOUT, LocalSocketAddress.Namespace.ABSTRACT));
+    	Log.i(TAG, "stdin/out connected");
+
+    }
+}