Note: the information was current as of node 0.7.0 top of tree, from circa Jan 21, 2012. So modify as necessary.
There are three major steps/challenges in getting node.js to compile on ARM
- We need to get V8 to compile on your target arch. Look at our previous article.
- Need to get all the node.js other dependencies to use the right toolchain.
- Make sure we tell node’s dependencies where the libs and includes are for your cross-environment.
We are going to assume that you have successfully got V8 compiling for your hardware. If not see Cross-compile V8 for ARM.
Understanding the node.js build process.
As of 0.6.x, node.js uses gyp (Generate Your Projects) from the Chromium project. GYP can generate Visual Studio, XCode projects, and on Linux Makefiles. For node, .gyp files located in the root and /deps dirs of the source tree direct generation of makefiles.
node is configured using a python script, configure, in the root of the source. Building node for execution on the build host is very easy (all examples are for Linux)
$ ./configure
{ 'target_defaults': { 'cflags': [],
'defines': [],
'include_dirs': [],
'libraries': ['-lz']},
'variables': { 'host_arch': 'ia32',
'node_install_npm': 'true',
'node_install_waf': 'true',
'node_prefix': '',
'node_shared_cares': 'false',
'node_shared_v8': 'false',
'node_use_dtrace': 'false',
'node_use_isolates': 'true',
'node_use_openssl': 'true',
'node_use_system_openssl': 'false',
'target_arch': 'ia32',
'v8_use_snapshot': 'true'}}
creating ./config.gypi
creating ./config.mk
$ make
configure has an option for cross compile, –dest-cpu=ARCH where it says arm, ia32 and x64 are valid architectures. However, you may be asking… great, but how does it know where my cross compile toolchain is? Well, it doesn’t. This is not a cross-compile option, just an architecture option.
Full cross compile support does not really seem to be baked into node.js yet. In order to cross-compile we are going to override some build vars, run the configure script, and then open a shell where we can build and do any other make targets we want. Below is a script which sets up a cross compile, using a specified toolchain, on Linux. However, your mileage may vary. Modify for your own purposes and try the script out. If you get some issues, then read on.
setup-cross.sh
#!/bin/sh -e
# some vars to specifcify our toolchain. Don't need to export
CROSS_BASE=/opt/freescale/usr/local/gcc-4.4.4-glibc-2.11.1-multilib-1.0/arm-fsl-linux-gnueabi
CROSS_INCLUDE=${CROSS_BASE}/arm-fsl-linux-gnueabi/multi-libs/usr/include/
CROSS_CC=${CROSS_BASE}/bin/arm-fsl-linux-gnueabi-gcc
PREFIX_BIN=$CROSS_BASE/bin/arm-fsl-linux-gnueabi
export CSTOOLS=$CROSS_BASE/bin
# cross library directory, should include stdc++:
export CSTOOLS_LIB=/opt/ltib/rootfs/lib
export CSTOOLS_USR_LIB=/opt/ltib/rootfs/usr/lib
# libc & system headers:
export CSTOOLS_INC=${CROSS_BASE}/arm-fsl-linux-gnueabi/multi-libs/usr/include/
export TARGET_ARCH="-march=armv5te"
#ARM9? try these: export TARGET_TUNE="-mtune=cortex-a8 -mfpu=neon
# -mfloat-abi=softfp -mthumb-interwork -mno-thumb" # optional
export TARGET_TUNE="-mtune=arm926ej-s -mfloat-abi=soft -mno-thumb-interwork"
export TOOL_PREFIX=$PREFIX_BIN
export CCFLAGS="-march=armv5te -mtune=arm926ej-s -mno-thumb-interwork -lstdc++"
export ARM_TARGET_LIB=$CTOOLS_LIB
export CPP="${PREFIX_BIN}-gcc -E"
export STRIP="${PREFIX_BIN}-strip"
export OBJCOPY="${PREFIX_BIN}-objcopy"
export AR="${PREFIX_BIN}-ar"
export F77="${PREFIX_BIN}-g77 ${TARGET_ARCH} ${TARGET_TUNE}"
unset LIBC
export RANLIB="${PREFIX_BIN}-ranlib"
export LD="${PREFIX_BIN}-ld"
export LDFLAGS="-L${CSTOOLS_USR_LIB} -L${CSTOOLS_LIB} -Wl,-rpath-link,${CSTOOLS_LIB} \
-Wl,-O1 -Wl,--hash-style=gnu"
export MAKE="make"
export CXXFLAGS="-isystem${CSTOOLS_INC} -fexpensive-optimizations \
-frename-registers \
-fomit-frame-pointer -O2 -ggdb3 -fpermissive -fvisibility-inlines-hidden"
export LANG="en_US.UTF-8"
export HOME="/home/ed"
export CCLD="${PREFIX_BIN}-gcc ${TARGET_ARCH} ${TARGET_TUNE}"
export PATH="${CSTOOLS}/bin:/opt/code-sourcery/arm-2009q1/bin/:\
${HOME}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
export CFLAGS="-isystem${CSTOOLS_INC} -fexpensive-optimizations \
-frename-registers \
-fomit-frame-pointer -O2 -ggdb3"
export OBJDUMP="arm-none-linux-gnueabi-objdump"
export CPPFLAGS="-isystem${CSTOOLS_INC}"
export CC="${PREFIX_BIN}-gcc ${TARGET_ARCH} ${TARGET_TUNE}"
export TERM="screen"
export SHELL="/bin/bash"
export CXX="${PREFIX_BIN}-g++ ${TARGET_ARCH} ${TARGET_TUNE}"
export NM="${PREFIX_BIN}-nm"
export AS="${PREFIX_BIN}-as"
# Configure node.js for cross-compile
# Try this first: ./configure --without-snapshot --dest-cpu=arm --gdb
./configure --dest-cpu=arm --gdb --shared-v8
bash --norc
For this script to work, I ended up needing to make some minor changes to node 0.7.0. For one, my build target is not an ARM 7. Two, my cross libraries are in a couple of locations. Also, I wanted to build with snapshot, and the cross-compile in my testing failed unless –without-snapshot was used on configure. Below are the tweaks:
Again, bear in mind this was top of tree, node 0.7.0 in Jan 2012, so you should check your source.
Correct floating point options, –march, etc.
In our other article on V8 we discussed how to choose the right options for your ARM arch toolchain. node’s configure script doesn’t allow you to set these, but our setup-cross.sh will set the options for building most of the support binaries. But the V8 configure will need more information… basically by default, node will try to build V8 for an ARM-7 architecture (with NEON and VFP3). How do we change this? In deps/v8/tools/gyp/v8.gyp you will notice a snippet:
# http://code.google.com/p/v8/issues/detail?id=914
'conditions': [
['armv7==1', {
# The ARM Architecture Manual mandates VFPv3 if NEON is
# available.
}],
],
...
},{ # else: armv7!=1
'variables': {
'mksnapshot_flags': [
'--noenable_armv7',
'--noenable_vfp3',
],
So we need armv7 to != 1. Since I could not find an configure option to fix this, I just fixed it with a small mod in the configure script.
def configure_node(o):
# TODO add gdb
o['variables']['node_use_isolates'] = b(not options.without_isolates)
...
o['variables']['armv7'] = "0" # add this if you are not >= armv7
Cross compile with snapshot
Take a glance at deps/v8/tools/gyp/v8.gyp at about line 90.
{
'target_name': 'v8_snapshot',
'type': '<(library)',
'conditions': [
['want_separate_host_toolset==1', {
'toolsets': ['host', 'target'],
'dependencies': ['mksnapshot#host', 'js2c#host'],
}, {
'toolsets': ['target'],
'dependencies': ['mksnapshot', 'js2c'],
}],
['component=="shared_library"', {
'conditions': [
['OS=="win"', {
'defines': [
'BUILDING_V8_SHARED',
],
'direct_dependent_settings': {
'defines': [
'USING_V8_SHARED',
],
},
If we want snapshot support, we need that mksnapshot#host. Othwerwise your cross compile will eventually fail, because it will not be able to run mksnapshot on the host (as it was compiled for the target). So we need want_separate_host_toolset to == 1.
We can fix this, again, by just adding a few lines into the configure script. Find the configure_node(o) function in configure, and add these lines:
def configure_node(o):
# TODO add gdb
o['variables']['node_use_isolates'] = b(not options.without_isolates)
...
if options.dest_cpu: # add these two lines
o['variables']['want_separate_host_toolset'] = '1'
# because if you specify a different target arch,
# you will need this to build v8 snapshot
You should now be able to use –with-snapshot for the cross-compile.
Note: Cross-compiling with snapshot only works on ia32 builds for the moment. I will post an update so cross-compiled will work on x64 when I figure out the problem.
Extra libraries
I ended up needing to point the build to another library location for libz. This could apply if you need to supply supplemental library directories for your hardware. This a target libz library.
parser.add_option("--libz-path",
action="store",
dest="libz_path",
help="libz location. default uses standard location.")
and
def configure_libz(o):
o['libraries'] += ['-lz']
if options.libz_path: # add these two lines
o['libraries'] += [' -L%s' % options.libz_path] #
This will pass down to the v8.gyp during configuration, and should force a build of a host mksnapshot if you specify a –dest-cpu. On your target, you will notice a faster startup of node.