From da228cf9d71b747f1824e85127039e5afc7effd8 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 5 Aug 2019 15:03:29 +0200 Subject: WIP --- library/build.gradle.kts | 11 +- .../java/akono/InstrumentedAkonoTests.kt | 30 +++ .../androidTest/kotlin/InstrumentedAkonoTests.kt | 27 -- library/src/main/cpp/CMakeLists.txt | 1 + library/src/main/cpp/akono-jni.cpp | 290 +++++++++++++++++---- library/src/main/java/akono/AkoniJni.kt | 39 +++ library/src/main/java/akono/Library.kt | 10 + library/src/main/kotlin/akono/AkoniJni.kt | 13 - library/src/main/kotlin/akono/Library.kt | 10 - library/src/test/java/akono/LibraryTest.kt | 16 ++ library/src/test/kotlin/akono/LibraryTest.kt | 16 -- 11 files changed, 342 insertions(+), 121 deletions(-) create mode 100644 library/src/androidTest/java/akono/InstrumentedAkonoTests.kt delete mode 100644 library/src/androidTest/kotlin/InstrumentedAkonoTests.kt create mode 100644 library/src/main/java/akono/AkoniJni.kt create mode 100644 library/src/main/java/akono/Library.kt delete mode 100644 library/src/main/kotlin/akono/AkoniJni.kt delete mode 100644 library/src/main/kotlin/akono/Library.kt create mode 100644 library/src/test/java/akono/LibraryTest.kt delete mode 100644 library/src/test/kotlin/akono/LibraryTest.kt (limited to 'library') diff --git a/library/build.gradle.kts b/library/build.gradle.kts index aa21ace1..b98510db 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,3 +1,4 @@ + plugins { id("com.android.library") kotlin("android") @@ -33,23 +34,13 @@ android { externalNativeBuild { cmake { - //path = File("src/main/cpp/CMakeLists.txt") setPath(file("src/main/cpp/CMakeLists.txt")) } } sourceSets { - // Work around a bug in the android plugin. - // Without this extra source set, test cases written in Kotlin are - // compiled but not executed. - named("androidTest") { - java.srcDir("src/androidTest/kotlin") - } - // Workaround for AndroidStudio named("main") { - java.srcDir("src/main/kotlin") jniLibs.srcDirs("../deps/compiled") - } } } diff --git a/library/src/androidTest/java/akono/InstrumentedAkonoTests.kt b/library/src/androidTest/java/akono/InstrumentedAkonoTests.kt new file mode 100644 index 00000000..b2e8e92a --- /dev/null +++ b/library/src/androidTest/java/akono/InstrumentedAkonoTests.kt @@ -0,0 +1,30 @@ +package akono.test; + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.runner.RunWith +import org.junit.Test +import androidx.test.filters.SmallTest +import androidx.test.filters.LargeTest +import org.junit.Assert.assertTrue +import org.junit.Assert.assertEquals +import akono.AkonoJni + +// @RunWith is required only if you use a mix of JUnit3 and JUnit4. +@RunWith(AndroidJUnit4::class) +@LargeTest +public class InstrumentedAkonoTestOne { + @Test + fun myJsTest() { + val ajni: AkonoJni = AkonoJni() + assertEquals("2", ajni.evalJs("1+1")) + assertEquals("36", ajni.evalJs("6*6")) + assertEquals("42", ajni.evalJs("(()=>{let x = 42; return x;})()")) + //assertEquals(null, ajni.evalJs("throw Error('hello exc')")) + //assertEquals(null, ajni.evalJs("undefinedX + undefinedY")) + //assertEquals("123", ajni.evalJs("console.log('hello world'); 123;")) + //assertEquals("123", ajni.evalJs("require")) + + assertEquals("undefined", ajni.evalJs("const myVal = 42")) + assertEquals("43", ajni.evalJs("myVal + 1")) + } +} diff --git a/library/src/androidTest/kotlin/InstrumentedAkonoTests.kt b/library/src/androidTest/kotlin/InstrumentedAkonoTests.kt deleted file mode 100644 index 6a59d0ef..00000000 --- a/library/src/androidTest/kotlin/InstrumentedAkonoTests.kt +++ /dev/null @@ -1,27 +0,0 @@ -package akono.test; - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.runner.RunWith -import org.junit.Test -import androidx.test.filters.SmallTest -import androidx.test.filters.LargeTest -import org.junit.Assert.assertTrue -import org.junit.Assert.assertEquals -import akono.AkonoJni - -// @RunWith is required only if you use a mix of JUnit3 and JUnit4. -@RunWith(AndroidJUnit4::class) -@LargeTest -public class InstrumentedAkonoTestOne { - @Test - fun myAkonoTest() { - val ajni: AkonoJni = AkonoJni() - assertEquals("foo", ajni.stringFromJNI()) - } - - @Test - fun myJsTest() { - val ajni: AkonoJni = AkonoJni() - assertEquals("2", ajni.evalJs("1+1")); - } -} diff --git a/library/src/main/cpp/CMakeLists.txt b/library/src/main/cpp/CMakeLists.txt index b101f4bf..72259215 100644 --- a/library/src/main/cpp/CMakeLists.txt +++ b/library/src/main/cpp/CMakeLists.txt @@ -16,6 +16,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") include_directories( ${deps_dir}/node/src ${deps_dir}/node/deps/v8/include + ${deps_dir}/node/deps/uv/include ) add_library(node SHARED IMPORTED) diff --git a/library/src/main/cpp/akono-jni.cpp b/library/src/main/cpp/akono-jni.cpp index 2131e0eb..f355362b 100644 --- a/library/src/main/cpp/akono-jni.cpp +++ b/library/src/main/cpp/akono-jni.cpp @@ -2,57 +2,257 @@ #include #include #include +#define NODE_WANT_INTERNALS 1 +#include +#include +#include +#include -/* This is a trivial JNI example where we use a native method - * to return a new VM String. See the corresponding Java source - * file located at: - * - * hello-jni/app/src/main/java/com/example/hellojni/HelloJni.java + + +static int pfd[2]; +static pthread_t thr; +static const char *tag = "myapp"; + + +static void *thread_func(void*) +{ + ssize_t rdsz; + char buf[128]; + while((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) { + if(buf[rdsz - 1] == '\n') --rdsz; + buf[rdsz] = 0; /* add null-terminator */ + __android_log_write(ANDROID_LOG_DEBUG, tag, buf); + } + return 0; +} + +static void mylog(const char *msg) +{ + __android_log_write(ANDROID_LOG_DEBUG, tag, msg); +} + +int start_logger(const char *app_name) +{ + tag = app_name; + + /* make stdout line-buffered and stderr unbuffered */ + setvbuf(stdout, 0, _IOLBF, 0); + setvbuf(stderr, 0, _IONBF, 0); + + /* create the pipe and redirect stdout and stderr */ + pipe(pfd); + dup2(pfd[1], 1); + dup2(pfd[1], 2); + + /* spawn the logging thread */ + if(pthread_create(&thr, 0, thread_func, 0) == -1) + return -1; + pthread_detach(thr); + return 0; +} + + + +/** + * Helper class to manage conversion from a JNI string to a C string. */ -extern "C" JNIEXPORT jstring JNICALL -Java_akono_AkonoJni_stringFromJNI(JNIEnv* env, jobject thiz) +class JStringValue { +private: + jstring m_jstr; + const char *m_cstr; + JNIEnv* m_env; +public: + JStringValue(JNIEnv* env, jstring s) : m_env(env), m_jstr(s) { + m_cstr = env->GetStringUTFChars(s, NULL); + } + ~JStringValue() { + m_env->ReleaseStringUTFChars(m_jstr, m_cstr); + } + const char *operator*() { + return m_cstr; + } +}; + + +/** + * Slightly more sane wrapper around node::Init + */ +static void InitNode(std::vector argv) { + int ret_exec_argc = 0; + int ret_argc = argv.size(); + const char **ret_exec_argv = nullptr; + + node::Init(&ret_argc, &argv[0], &ret_exec_argc, &ret_exec_argv); +} + + +class NativeAkonoInstance { +private: + static bool logInitialized; + static bool v8Initialized; + //static std::unique_ptr platform; + static node::MultiIsolatePlatform *platform; +public: + v8::Isolate *isolate; + node::Environment *environment; + v8::Persistent globalContext; + + NativeAkonoInstance() : globalContext() { + + if (!logInitialized) { + start_logger("myapp"); + logInitialized = true; + } + + if (!v8Initialized) { + //platform = v8::platform::NewDefaultPlatform(); + //v8::V8::InitializePlatform(platform.get()); + //v8::V8::Initialize(); + + // Here, only the arguments used to initialize the global node/v8 platform + // are relevant, the others are skipped. + InitNode(std::vector{"node", "--trace-events-enabled"}); + platform = node::InitializeV8Platform(10); + v8::V8::Initialize(); + + v8Initialized = true; + } + + + node::ArrayBufferAllocator *allocator = node::CreateArrayBufferAllocator(); + this->isolate = node::NewIsolate(allocator, uv_default_loop()); + + + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + + node::IsolateData *isolateData = node::CreateIsolateData( + this->isolate, + uv_default_loop(), + platform, + allocator); + + + globalContext.Reset(isolate, v8::Context::New(isolate)); + + // Arguments for node itself + std::vector nodeArgv{"node", "-e", "console.log('hello world');"}; + // Arguments for the script run by node + std::vector nodeExecArgv{}; + + mylog("entering global scopt"); + + v8::Context::Scope context_scope(globalContext.Get(isolate)); + + mylog("creating environment"); + + node::Environment *environment = node::CreateEnvironment( + isolateData, + globalContext.Get(isolate), + nodeArgv.size(), + &nodeArgv[0], + nodeExecArgv.size(), + &nodeExecArgv[0]); + + mylog("loading environment"); + + node::LoadEnvironment(environment); + + mylog("finished environment"); + + //v8::Isolate::CreateParams create_params; + //create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); + //this->isolate = v8::Isolate::New(create_params); +// +// node::IsolateData *isolateData = node::CreateIsolateData() +// node::Environment *environment = node::CreateEnvironment(isolateData); + } + + ~NativeAkonoInstance() { + //this->isolate->Dispose(); + } + + jstring evalJs(JNIEnv* env, jstring sourceString) { + mylog("begin evalJs"); + + JStringValue jsv(env, sourceString); + + v8::Isolate::Scope isolate_scope(isolate); + + // Create a stack-allocated handle scope. + v8::HandleScope handle_scope(isolate); + + // Create a new context. + //v8::Local context = v8::Context::New(isolate); + + v8::Local context = globalContext.Get(isolate); + + // Enter the context for compiling and running the hello world script. + v8::Context::Scope context_scope(context); + + { + // Create a string containing the JavaScript source code. + v8::Local source = + v8::String::NewFromUtf8(isolate, *jsv, + v8::NewStringType::kNormal) + .ToLocalChecked(); + + // Compile the source code. + v8::Local script; + + mylog("about to compile script"); + + if (!v8::Script::Compile(context, source).ToLocal(&script)) { + return nullptr; + } + + mylog("about to run script"); + + // Run the script to get the result. + v8::Local result; + if (!script->Run(context).ToLocal(&result)) { + mylog("running script failed"); + return nullptr; + } + + mylog("converting script result value"); + + // Convert the result to an UTF8 string and print it. + v8::String::Utf8Value utf8(isolate, result); + + mylog("about to return value"); + + return env->NewStringUTF(*utf8); + } + } +}; + + +bool NativeAkonoInstance::v8Initialized = false; +bool NativeAkonoInstance::logInitialized = false; +node::MultiIsolatePlatform *NativeAkonoInstance::platform = nullptr; + + +extern "C" JNIEXPORT jobject JNICALL +Java_akono_AkonoJni_initNative(JNIEnv* env, jobject thiz) +{ + NativeAkonoInstance *myInstance = new NativeAkonoInstance(); + return env->NewDirectByteBuffer(myInstance, 0); +} + + +extern "C" JNIEXPORT void JNICALL +Java_akono_AkonoJni_destroyNative(JNIEnv* env, jobject thiz, jobject buf) { -#if defined(__arm__) - #if defined(__ARM_ARCH_7A__) - #if defined(__ARM_NEON__) - #if defined(__ARM_PCS_VFP) - #define ABI "armeabi-v7a/NEON (hard-float)" - #else - #define ABI "armeabi-v7a/NEON" - #endif - #else - #if defined(__ARM_PCS_VFP) - #define ABI "armeabi-v7a (hard-float)" - #else - #define ABI "armeabi-v7a" - #endif - #endif - #else - #define ABI "armeabi" - #endif -#elif defined(__i386__) -#define ABI "x86" -#elif defined(__x86_64__) -#define ABI "x86_64" -#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ -#define ABI "mips64" -#elif defined(__mips__) -#define ABI "mips" -#elif defined(__aarch64__) -#define ABI "arm64-v8a" -#else -#define ABI "unknown" -#endif - - return env->NewStringUTF("Hello from JNI ! Compiled with ABI " ABI "."); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + delete myInstance; } extern "C" JNIEXPORT jstring JNICALL -Java_akono_AkonoJni_evalJs(JNIEnv* env, jobject thiz, jstring source) +Java_akono_AkonoJni_evalJs(JNIEnv* env, jobject thiz, jstring sourceStr, jobject buf) { - std::unique_ptr platform = v8::platform::NewDefaultPlatform(); - v8::V8::InitializePlatform(platform.get()); - v8::V8::Initialize(); - return env->NewStringUTF("Hello World"); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + return myInstance->evalJs(env, sourceStr); } diff --git a/library/src/main/java/akono/AkoniJni.kt b/library/src/main/java/akono/AkoniJni.kt new file mode 100644 index 00000000..2210a7ef --- /dev/null +++ b/library/src/main/java/akono/AkoniJni.kt @@ -0,0 +1,39 @@ +package akono + +import java.nio.ByteBuffer + +typealias AkonoNativePointer = ByteBuffer + +class AkonoJni { + external fun stringFromJNI(): String + + private external fun evalJs(source: String, p: AkonoNativePointer): String + + private external fun destroyNative(b: AkonoNativePointer) + private external fun initNative(nodeArgv: Array): AkonoNativePointer + + private external fun runNodeLoop(b: AkonoNativePointer) + + private external fun postMessageToNode(message: String, b: AkonoNativePointer) + + private external fun waitForMessageFromNode(b: AkonoNativePointer): String + + private var internalNativePointer: AkonoNativePointer + + fun evalJs(source: String): String = evalJs(source, internalNativePointer) + + @Override + protected fun finalize() { + destroyNative(internalNativePointer) + } + + constructor(vararg nodeArgv: String) { + internalNativePointer = initNative(nodeArgv) + } + + companion object { + init { + System.loadLibrary("akono-jni") + } + } +} \ No newline at end of file diff --git a/library/src/main/java/akono/Library.kt b/library/src/main/java/akono/Library.kt new file mode 100644 index 00000000..920648fd --- /dev/null +++ b/library/src/main/java/akono/Library.kt @@ -0,0 +1,10 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package akono + +class Library { + fun someLibraryMethod(): Boolean { + return true + } +} diff --git a/library/src/main/kotlin/akono/AkoniJni.kt b/library/src/main/kotlin/akono/AkoniJni.kt deleted file mode 100644 index e37bef0e..00000000 --- a/library/src/main/kotlin/akono/AkoniJni.kt +++ /dev/null @@ -1,13 +0,0 @@ -package akono; - -class AkonoJni { - external fun stringFromJNI(): String; - - external fun evalJs(source: String): String; - - companion object { - init { - System.loadLibrary("akono-jni") - } - } -} diff --git a/library/src/main/kotlin/akono/Library.kt b/library/src/main/kotlin/akono/Library.kt deleted file mode 100644 index 920648fd..00000000 --- a/library/src/main/kotlin/akono/Library.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * This Kotlin source file was generated by the Gradle 'init' task. - */ -package akono - -class Library { - fun someLibraryMethod(): Boolean { - return true - } -} diff --git a/library/src/test/java/akono/LibraryTest.kt b/library/src/test/java/akono/LibraryTest.kt new file mode 100644 index 00000000..1a16e7e6 --- /dev/null +++ b/library/src/test/java/akono/LibraryTest.kt @@ -0,0 +1,16 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package akono + +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.test.assertEquals + +import akono.AkonoJni + +class LibraryTest { + @Test fun testSomeLibraryMethod() { + assertTrue(true) + } +} diff --git a/library/src/test/kotlin/akono/LibraryTest.kt b/library/src/test/kotlin/akono/LibraryTest.kt deleted file mode 100644 index 1a16e7e6..00000000 --- a/library/src/test/kotlin/akono/LibraryTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * This Kotlin source file was generated by the Gradle 'init' task. - */ -package akono - -import kotlin.test.Test -import kotlin.test.assertTrue -import kotlin.test.assertEquals - -import akono.AkonoJni - -class LibraryTest { - @Test fun testSomeLibraryMethod() { - assertTrue(true) - } -} -- cgit v1.2.3