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.

android-configure script

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)',
},
...

to

...
'mkpeephole_exec': 'path/to/x86/mkpeephole',
},
...

getservbyport_r missing

When I was building an older version there were additionally some errors related to getservbyport_r missing. To fix this I just made sure HAVE_GETSERVBYPORT_R is not defined in deps/cares/config/android/ares_config.h

/* 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 System.loadLibary(). 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

LDFLAGS=-shared make

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=$(@F) with 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.

Next Post

Add a comment