Building Node.js for Android
I wanted to learn more about Node.js because I wanted to use it in some of my experimental Android projects. I tried to cross-compile Node.js version v7.7.3 on Ubuntu 16 with cross-compilation toolchain generated by Android NDK version r13b. Building Node for Android was relatively easy, but I still had some issues while building and I needed to do some additional steps to make it work. This post is mostly a summary of my notes that I've taken that covers how to fix the build errors that appeared during the build process. I did not try to fix the complete build system and some of these steps are just quick hacks to make it work.
According to 'BUILDING.md' document found in the source tree, Android is not officially supported. However, there is a
android-configure script that aids in building Node.js for Android. In my case, I needed to make some changes to the script to make it work.
'Refusing to clobber existing install directory'
This was the first error that occurred after running
source ./android-configure path/to/ndk arm
I was using Android NDK r13b and to fix this error I only needed to add the
--force parameter to the line where
make-standalone-toolchain.sh script was executed (somewhere around line 43)
$1/build/tools/make-standalone-toolchain.sh \ --toolchain=$TOOLCHAIN_NAME-$CC_VER \ --arch=$ARCH \ --install-dir=$TOOLCHAIN \ --platform=android-21 --force
'/system/bin/linker: No such file or directory'
This error seems to be caused by running an executable that was compiled for Android on my Ubuntu host. After running the configure script, the build process seems to be configured to build all tools for Android arm, but some of the tools are executed on my x86 Ubuntu host machine as part of the build process and therefore must be build for intel/x86. To fix this issue I only needed to do 2 things.
First thing I did was removing Intl support from build by running the
configure script with
--without-intl parameter. The error was caused by running icupkg executable that was build for arm on my intel host as part of build process. Intl is ECMAScript internationalization api and Node.js uses icu tools/libraries to implement Intl. For my project I didn't need
this, so I just removed it. The ./configure part inside of android-configure was changed to this
./configure \ --dest-cpu=$DEST_CPU \ --dest-os=android \ --without-snapshot \ --openssl-no-asm \ --without-intl
The build process also tried to execute the
mkpeephole tool on my intel host which was also build for arm. This caused the same error. So the second thing I did to fix it was that I've first build Node for my Ubuntu machine (intel) by running configure/make without parameters. Then I changed
deps/v8/src/v8.gyp to point to the mkpeephole executable build for x86 machine. I changed the line
... 'mkpeephole_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)mkpeephole<(EXECUTABLE_SUFFIX)', }, ...
... 'mkpeephole_exec': 'path/to/x86/mkpeephole', }, ...
When I was building an older version there were additionally some errors related to getservbyport_r missing. To fix this I just
HAVE_GETSERVBYPORT_R is not defined in
/* Define to 1 if you have the getservbyport_r function. */ //#define HAVE_GETSERVBYPORT_R 1
Building as a shared library
The previous steps fixed most of the issues and I could just have used the produced
node executable. However, I preferred to use node as shared library. With shared library(.so), the Android system takes care of extracting it from APK file and copying it into proper directory. You can then load the library simply by calling
The usual way to build a shared library is to add the
--shared parameter when executing the configure script. So I changed my configure parameters to the following
./configure --without-snapshot --without-npm --without-intl --dest-cpu=arm --dest-os=android --openssl-no-asm --shared
This, however, might not be enough. I had to deal with 2 issues.
First I got
undefined reference to main. This can be fixed by additionally providing LDFLAGS variable when calling make, like this
The second issue is with the SONAME and the version number that is appended to the name of the shared library. If you run
objdump -p out/Release/lib.target/libnode.so.51 | grep SONAME, you can see that there is a version appended. Android doesn't support this. So the next thing I did was to remove version number from soname. I didn't have time to play with the configuration scripts, so I just changed the
-soname parameter inside of the generated
out/Makefile. I manually replaced
soname=libnode.so. After that I just renamed the final shared library file to libnode.so and I was able to use it in my app.
I'll write about how to embed the generated libnode.so into an Android app in my next post.