Skip to content

Commit

Permalink
android: native modules support
Browse files Browse the repository at this point in the history
Adds native modules support to Android builds by including new
gradle tasks to copy the nodejs-project and build the npm native
modules for each of the needed CPU architectures. The resulting
binaries are then added as a different asset folder for each
architecture, to be copied and loaded at runtime depending on which
architecture is needed.
The npm tasks can be activated by setting the environment variable
'NODEJS_MOBILE_BUILD_NATIVE_MODULES' to 1.
  • Loading branch information
jaimecbernardo committed Feb 12, 2018
1 parent 448c9ae commit af82e39
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 2 deletions.
152 changes: 150 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
classpath 'com.android.tools.build:gradle:2.2.3'
}
}

Expand All @@ -27,7 +27,7 @@ android {
}
}
ndk {
abiFilters "armeabi-v7a", "x86"
abiFilters = project(":app").android.defaultConfig.ndk.abiFilters
}
}

Expand Down Expand Up @@ -85,3 +85,151 @@ task GenerateNodeProjectAssetsLists {
}

tasks.getByPath(":${project.name}:preBuild").dependsOn GenerateNodeProjectAssetsLists

import org.gradle.internal.os.OperatingSystem;

if ("1".equals(System.getenv('NODEJS_MOBILE_BUILD_NATIVE_MODULES'))) {
task CleanNPMTempBuildDir (type: Delete) {
delete "${rootProject.buildDir}/nodejs-native-assets-temp-build/"
}

android.defaultConfig.ndk.abiFilters.each { abi_name ->
String temp_arch = {
switch (abi_name) {
case 'armeabi-v7a':
'arm'
break
case 'arm64-v8a':
'arm64'
break
default:
abi_name
break
}
}()
String temp_cc_ver = '4.9';
String temp_dest_cpu;
String temp_v8_arch;
String temp_suffix;
String temp_toolchain_name;
switch ( temp_arch )
{
case 'arm':
temp_dest_cpu = "${temp_arch}"
temp_v8_arch = "${temp_arch}"
temp_suffix = "${temp_arch}-linux-androideabi"
temp_toolchain_name = "${temp_suffix}"
break
case 'x86':
temp_dest_cpu = 'ia32'
temp_v8_arch = 'ia32'
temp_suffix = 'i686-linux-android'
temp_toolchain_name = "${temp_arch}"
break
case 'x86_64':
temp_dest_cpu = 'x64'
temp_v8_arch = 'x64'
temp_suffix = "${temp_arch}-linux-android"
temp_toolchain_name = "${temp_arch}"
break
case 'arm64':
temp_dest_cpu = "${temp_arch}"
temp_v8_arch = "${temp_arch}"
temp_suffix = 'aarch64-linux-android'
temp_toolchain_name = 'aarch64'
break
default:
throw new GradleException("Unsupported architecture for nodejs-mobile native modules: ${temp_arch}")
break
}

String ndk_bundle_path = android.ndkDirectory
String standalone_toolchain = "${rootProject.buildDir}/standalone-toolchains/${temp_toolchain_name}"
String npm_toolchain_add_to_path = "${rootProject.buildDir}/bin"
String npm_toolchain_ar = "${standalone_toolchain}/bin/${temp_suffix}-ar"
String npm_toolchain_cc = "${standalone_toolchain}/bin/${temp_suffix}-clang"
String npm_toolchain_cxx = "${standalone_toolchain}/bin/${temp_suffix}-clang++"
String npm_toolchain_link = "${standalone_toolchain}/bin/${temp_suffix}-clang++"

String npm_gyp_defines = "target_arch=${temp_arch}"
npm_gyp_defines += " v8_target_arch=${temp_v8_arch}"
npm_gyp_defines += " android_target_arch=${temp_arch}"
if (OperatingSystem.current().isMacOsX()) {
npm_gyp_defines += " host_os=mac OS=android"
} else if (OperatingSystem.current().isLinux()) {
npm_gyp_defines += " host_os=linux OS=android"
} else {
throw new GradleException("Unsupported opperating system for nodejs-mobile native builds: ${OperatingSystem.current().getName()}")
}


task "CopyNodeProjectAssets${abi_name}" (type:Copy) {
dependsOn "CleanNPMTempBuildDir"
description = "Copying node assets to build native modules for ${abi_name}."
from "${rootProject.projectDir}/../nodejs-assets/nodejs-project/"
into "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/"
}

task "MakeToolchain${abi_name}" (type:Exec) {
dependsOn "CopyNodeProjectAssets${abi_name}"
description = "Building a native toolchain to compile nodejs-mobile native modules for ${abi_name}."
executable = "${ndk_bundle_path}/build/tools/make-standalone-toolchain.sh"
args "--toolchain=${temp_toolchain_name}-${temp_cc_ver}", "--arch=${temp_arch}", "--install-dir=${standalone_toolchain}", "--stl=libc++", "--force", "--platform=android-21"
}

task "BuildNpmModules${abi_name}" (type:Exec) {
dependsOn "MakeToolchain${abi_name}"
description = "Building native modules for ${abi_name}."
workingDir "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/"
commandLine 'npm', '--verbose', 'rebuild', '--build-from-source'
//environment ('PATH', "${npm_toolchain_add_to_path}" + System.getProperty("path.separator") + "${System.env.PATH}")
environment ('npm_config_node_engine', 'v8' )
environment ('npm_config_nodedir', "${project.projectDir}/libnode/" )
environment ('npm_config_arch', temp_arch)
environment ('npm_config_platform', 'android')
environment ('npm_config_format', 'make-android')
environment ('TOOLCHAIN',"${standalone_toolchain}")
environment ('AR',"${npm_toolchain_ar}")
environment ('CC',"${npm_toolchain_cc}")
environment ('CXX',"${npm_toolchain_cxx}")
environment ('LINK',"${npm_toolchain_link}")
environment ('GYP_DEFINES',"${npm_gyp_defines}")
}
task "CleanFinalNpmAssets${abi_name}" (type: Delete) {
delete "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/"
}
task "CopyBuiltNpmAssets${abi_name}" (type:Copy) {
dependsOn "BuildNpmModules${abi_name}"
dependsOn "CleanFinalNpmAssets${abi_name}"
description = "Copying node assets with build native modules for ${abi_name}."
from "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/"
into "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/"
includeEmptyDirs = false
include '**/*.node'
project.android.sourceSets.main.assets.srcDirs+="${rootProject.buildDir}/nodejs-native-assets/"
}

task "GenerateNodeNativeAssetsLists${abi_name}" {
dependsOn "CopyBuiltNpmAssets${abi_name}"
description "Generates a list for runtime copying"
doLast{
String file_list = "";
String dir_list = "";

def assets_tree = fileTree(dir: "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/" )
assets_tree.visit { assetFile ->
if (assetFile.isDirectory()) {
dir_list+="${assetFile.relativePath}\n"
} else {
file_list+="${assetFile.relativePath}\n"
}
}
def file_list_path = new File( "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/file.list")
file_list_path.write file_list
def dir_list_path = new File( "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/dir.list")
dir_list_path.write dir_list
}
}
tasks.getByPath(":${project.name}:preBuild").dependsOn "GenerateNodeNativeAssetsLists${abi_name}"
}
}
20 changes: 20 additions & 0 deletions android/src/main/cpp/native-lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ extern "C" int callintoNode(int argc, char *argv[])
return exit_code;
}

#if defined(__arm__)
#define CURRENT_ABI_NAME "armeabi-v7a"
#elif defined(__aarch64__)
#define CURRENT_ABI_NAME "arm64-v8a"
#elif defined(__i386__)
#define CURRENT_ABI_NAME "x86"
#elif defined(__x86_64__)
#define CURRENT_ABI_NAME "x86_64"
#else
#error "Trying to compile for an unknown ABI."
#endif

extern "C"
JNIEXPORT jstring JNICALL
Java_com_janeasystems_rn_1nodejs_1mobile_RNNodeJsMobileModule_getCurrentABIName(
JNIEnv *env,
jobject /* this */) {
return env->NewStringUTF(CURRENT_ABI_NAME);
}

#define APPNAME "RNBRIDGE"

void rcv_message(char* msg) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ public class RNNodeJsMobileModule extends ReactContextBaseJavaModule {
private static final String TRASH_DIR = "nodejs-project-trash";
private static final String SHARED_PREFS = "NODEJS_MOBILE_PREFS";
private static final String LAST_UPDATED_TIME = "NODEJS_MOBILE_APK_LastUpdateTime";
private static final String BUILTIN_NATIVE_ASSETS_PREFIX = "nodejs-native-assets-";

private static String trashDirPath;
private static String filesDirPath;
private static String nodeJsProjectPath;
private static String builtinModulesPath;
private static String nativeAssetsPath;

private static long lastUpdateTime = 1;
private static long previousLastUpdateTime = 0;
Expand Down Expand Up @@ -64,6 +66,7 @@ public RNNodeJsMobileModule(ReactApplicationContext reactContext) {
nodeJsProjectPath = filesDirPath + "/" + NODEJS_PROJECT_DIR;
builtinModulesPath = filesDirPath + "/" + NODEJS_BUILTIN_MODULES;
trashDirPath = filesDirPath + "/" + TRASH_DIR;
nativeAssetsPath = BUILTIN_NATIVE_ASSETS_PREFIX + getCurrentABIName();

asyncInit();
}
Expand Down Expand Up @@ -195,6 +198,8 @@ public void run() {
}
}

public native String getCurrentABIName();

public native Integer startNodeWithArguments(String[] arguments, String modulesPath, boolean option_redirectOutputToLogcat);

public native void notifyNode(String msg);
Expand Down Expand Up @@ -257,6 +262,29 @@ private static boolean deleteFolderRecursively(File file) {
}
}

private boolean copyNativeAssetsFrom() throws IOException {
// Load the additional asset folder and files lists
ArrayList<String> nativeDirs = readFileFromAssets(nativeAssetsPath + "/dir.list");
ArrayList<String> nativeFiles = readFileFromAssets(nativeAssetsPath + "/file.list");
// Copy additional asset files to project working folder
if (nativeFiles.size() > 0) {
Log.v(TAG, "Building folder hierarchy for " + nativeAssetsPath);
for (String dir : nativeDirs) {
new File(nodeJsProjectPath + "/" + dir).mkdirs();
}
Log.v(TAG, "Copying assets using file list for " + nativeAssetsPath);
for (String file : nativeFiles) {
String src = nativeAssetsPath + "/" + file;
String dest = nodeJsProjectPath + "/" + file;
copyAsset(src, dest);
}
} else {
Log.v(TAG, "No assets to copy from " + nativeAssetsPath);
}
return true;
}


private void copyNodeJsAssets() throws IOException {
assetManager = getReactApplicationContext().getAssets();

Expand Down Expand Up @@ -288,6 +316,8 @@ private void copyNodeJsAssets() throws IOException {
copyAssetFolder(NODEJS_PROJECT_DIR, nodeJsProjectPath);
}

copyNativeAssetsFrom();

// Do the builtin-modules copy too.
// If a previous built-in modules folder is present, delete it.
File modulesDirReference = new File(builtinModulesPath);
Expand Down

0 comments on commit af82e39

Please sign in to comment.